Switch to bioconductor mode

library(pagoda2)
Warning message:
package ‘igraph’ was built under R version 3.5.3 
library(conos)
library(parallel)
library(magrittr)
library(ggplot2)
library(pbapply)
library(tibble)
library(dplyr)
library(ggrastr)
library(cowplot)
library(ggbeeswarm)
library(readr)
library(pheatmap)
library(reshape2)
library(clusterProfiler)
library(DOSE)
library(org.Hs.eg.db)
library(enrichplot)

require(ComplexHeatmap)
require(circlize)

Load de results:

x <- list.files(path='..',pattern='de.*.rds'); names(x) <- gsub("de.(.*).rds","\\1",x)
del <- lapply(x,function(n) readRDS(paste0('../',n)))

Prepare GO categories …

 go_datas <- c("BP", "CC", "MF") %>% setNames(., .) %>%
   pblapply(function(n) clusterProfiler:::get_GO_data(org.Hs.eg.db, n, "ENTREZID") %>%
              as.list() %>% as.environment()) # otherwise it pass reference to the environment content

Run enrichment tests

enrichGOOpt <- function (gene, OrgDB, goData, keyType = "ENTREZID", ont = "MF", pvalueCutoff = 0.05,
                         pAdjustMethod = "BH", universe=NULL, qvalueCutoff = 0.2, minGSSize = 10,
                         maxGSSize = 500, readable = FALSE, pool = FALSE) {
  ont %<>% toupper %>% match.arg(c("BP", "CC", "MF"))

  res <- clusterProfiler:::enricher_internal(gene, pvalueCutoff = pvalueCutoff,
                                             pAdjustMethod = pAdjustMethod, universe = universe,
                                             qvalueCutoff = qvalueCutoff, minGSSize = minGSSize,
                                             maxGSSize = maxGSSize, USER_DATA = goData)
  if (is.null(res))
    return(res)

  res@keytype <- keyType
  res@organism <- clusterProfiler:::get_organism(OrgDB)
  if (readable) {
    res <- DOSE::setReadable(res, OrgDB)
  }
  res@ontology <- ont

  return(res)
}

distanceBetweenTerms <- function(go.df) {
  genes.per.go <- sapply(go.df$geneID, strsplit, "/") %>% setNames(go.df$Description)
  all.go.genes <- unique(unlist(genes.per.go))
  all.gos <- unique(go.df$Description)

  genes.per.go.mat <- matrix(0, length(all.go.genes), length(all.gos)) %>%
    `colnames<-`(all.gos) %>% `rownames<-`(all.go.genes)

  for (i in 1:length(genes.per.go)) {
    genes.per.go.mat[genes.per.go[[i]], go.df$Description[[i]]] <- 1
  }

  return(dist(t(genes.per.go.mat), method="binary"))
}
calculate.gos <- function(de,n.top.genes=300,n.cores=1) {
  de <- de[unlist(lapply(de,is.list))]
  
  # add Z scores
  de <- lapply(de,function(d) {
    res.table <- d$res;
    res.table$Z <- -qnorm(res.table$pval/2)
    res.table$Z[is.na(res.table$Z)] <- 0
    res.table$Za <- -qnorm(res.table$padj/2)
    res.table$Za[is.na(res.table$Za)] <- 0
    res.table$Z <- res.table$Z  * sign(res.table$log2FoldChange)
    res.table$Za <- res.table$Za  * sign(res.table$log2FoldChange)
    d$res <- res.table;
    d
  })
  
  
  gns <- list(down=lapply(de,function(x) rownames(x$res)[order(x$res$Z,decreasing=F)[1:n.top.genes]]),
              up=lapply(de,function(x) rownames(x$res)[order(x$res$Z,decreasing=T)[1:n.top.genes]]),
              all=list(all=unique(unlist(lapply(de,function(x) rownames(x$res))))))
  
  gns.entrez <- lapply(gns,function(x) lapply(x, bitr, 'SYMBOL', 'ENTREZID', org.Hs.eg.db) %>% lapply(`[[`, "ENTREZID"))
  
  gos <- lapply(gns.entrez[c('up','down')],function(gns) {
    lapply(gns,enrichGOOpt,universe=gns.entrez$all$all, ont='BP', goData=go_datas[['BP']], readable=T, OrgDB=org.Hs.eg.db)# %>% lapply(function(x) x@result)
  })
  
}

gos.cluster <- function(gos,n.clusters=20,max.pval=0.05) {
  gos_filt <- lapply(gos,function(x) filter(x@result,p.adjust<max.pval))
  gos_joint <- do.call(rbind,gos_filt)
  
  gos_joint <- gos_filt %>% .[sapply(., nrow) > 0] %>% names() %>% setNames(., .) %>% lapply(function(n) cbind(gos_filt[[n]],Type=n)) %>% Reduce(rbind,.)
  go_dist <- distanceBetweenTerms(gos_joint)
  clusts <- hclust(go_dist,method='ward.D2') %>% cutree(n.clusters)
  gos_per_clust <- split(names(clusts), clusts)
  ngos_per_clust <- sapply(gos_per_clust, length)
  #table(clusts)
  
  gos_per_clust <- split(names(clusts), clusts)
  gos_joint %<>% mutate(GOClust=clusts[Description])
  name_per_clust <- gos_joint %>% group_by(GOClust, Description) %>% summarise(pvalue=exp(mean(log(pvalue)))) %>% 
    split(.$GOClust) %>% sapply(function(df) df$Description[which.min(df$pvalue)])
  gos_joint %<>% mutate(GOClustName=name_per_clust[as.character(GOClust)])
  
  # cluster summary
  go_bp_summ_df <- gos_joint %>% group_by(Type, GOClustName) %>% 
    summarise(p.adjust=min(p.adjust)) %>% ungroup() %>% mutate(p.adjust=-log10(p.adjust)) %>% 
    tidyr::spread(Type, p.adjust) %>% as.data.frame() %>% set_rownames(.$GOClustName) %>% .[, 2:ncol(.)] #%>% .[, type_order[type_order %in% colnames(.)]]
  go_bp_summ_df[is.na(go_bp_summ_df)] <- 0
  
  return(list(joint=gos_joint,clusters=clusts,summary=go_bp_summ_df))
}

Calculate enrichments

gosl <- lapply(del,calculate.gos)

Calculate and plot clusters:

cols <- list(up=colorRamp2(c(0, 6), c("grey98", "red")),down=colorRamp2(c(0, 6), c("grey98", "blue")))
n.clusters <- 20; max.pval <- 0.05;

Involved vs. Benign

cx <- lapply(gosl$IB,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.IB.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.IB.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Figure output with fixed cell type ordering

ord <- hclust(dist(t(rbind(cx$up$summary,cx$down$summary))))$order;
mu <- Heatmap(as.matrix(cx$up$summary[,ord]),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),column_names_gp = gpar(fontsize = 11),cluster_columns=F,show_heatmap_legend =F)
md <- Heatmap(as.matrix(cx$down$summary[,ord]),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),column_names_gp = gpar(fontsize = 11),cluster_columns=F,show_heatmap_legend =F)
pdf(file='de.IB.up.pdf',width=5.5,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.IB.down.pdf',width=5.5,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Tumor vs. Benign

cx <- lapply(gosl$TB,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.TB.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.TB.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Involved vs. Distal

cx <- lapply(gosl$ID,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.ID.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.ID.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Try plotting individual results:

x <- gosl2$IB$up$Progenitors
barplot(x, showCategory=20)

x <- gosl$IB$up$Progenitors
p <- dotplot(x, showCategory=20,title='Upregulated in Progenitors') +scale_colour_gradient(low = "red", high = "gray80") +xlab('fraction of genes') +scale_x_continuous(breaks=pretty(unlist(lapply(x@result$GeneRatio[1:20],function(x) eval(parse(text=x)))),3))
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
ggsave('IB.Progenitor.up.pdf',width=6,height=6,plot=p)
p

x <- gosl$IB$down$Progenitors
p <- dotplot(x, showCategory=20,title='Downregulated in Progenitors') +scale_colour_gradient(low = "blue", high = "gray80")+xlab('fraction of genes')+scale_x_continuous(breaks=pretty(unlist(lapply(x@result$GeneRatio[1:20],function(x) eval(parse(text=x)))),3))
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
ggsave('IB.Progenitor.down.pdf',width=6,height=6,plot=p)
p

x <- gosl2$IB$up$pDC
p <- dotplot(x, showCategory=20,title='Upregulated in pDC') +scale_colour_gradient(low = "red", high = "gray80") +xlab('fraction of genes')
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
ggsave('IB.pDC.up.pdf',width=8,height=6,plot=p)
p

x <- gosl2$IB$down$pDC
p <- dotplot(x, showCategory=20,title='Downregulated in pDC') +scale_colour_gradient(low = "blue", high = "gray80")
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
ggsave('IB.pDC.down.pdf',width=8,height=6,plot=p)
p

Additional comparisons (paired, corrected)

Load Shenglin’s results

pw <- '/home/meisl/Workplace/BMME/Revision/DE/'
x <- list(cTB='de.correction.Tumor.vs.Benign.rds',pID='de.paired.Involved.vs.Distal.rds',pTD='de.paired.Tumor.vs.Distal.rds')
del2 <- lapply(x,function(x) readRDS(paste0(pw,x)))

del2$pID <- readRDS("../de.pID.rds")
del2$pTD <- readRDS("../de.pTD.rds")

gosl2 <- lapply(del2,calculate.gos)

Corrected Tumor vs. Benign

cx <- lapply(gosl2$cTB,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.cTB.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.cTB.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Paired Tumor vs. Distal

cx <- lapply(gosl2$pTD,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.pTD.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.pTD.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Paired Involved vs. Distal

cx <- lapply(gosl2$pID,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)
mu <- Heatmap(as.matrix(cx$up$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
md <- Heatmap(as.matrix(cx$down$summary),col=cols$down,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10))
pdf(file='de.pID.up.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
null device 
          1 
pdf(file='de.pID.down.pdf',width=7,height=4.5); draw(md, heatmap_legend_side = "left"); dev.off();
null device 
          1 
mu; md;

Redraw heatmaps

sn <- function(x) setNames(x,x)
drawOverlapHetmap=function(Defile,dec=NULL,n.top.genes=300){
  
  nn=names(Defile)
  sname=nn[unlist(lapply(Defile, function(x) 'res' %in% names(x)))]
  
  
  glist= lapply(sn(sname), function(x){ 
    tmp1=Defile[[x]]$res
    tmp1=addZscore(tmp1,dec=dec)
    rownames(tmp1)[1:n.top.genes]
  })
  
  Zscore= lapply(sn(sname), function(x){ 
    tmp1=Defile[[x]]$res
    tmp1=addZscore(tmp1,dec=dec)
    ss=tmp1$Z
    names(ss)=rownames(tmp1)
    ss
  })
  
  
  
  rr=listToOverlapMatrix2(glist)
  
  for( i in seq(nrow(rr))){
    rr[i,i]=NA
  }
  
  rr
}

addZscore=function(res.table,dec=NULL){
  res.table$Z <- -qnorm(res.table$pval/2)
  res.table$Z[is.na(res.table$Z)] <- 0
  res.table$Za <- -qnorm(res.table$padj/2)
  res.table$Za[is.na(res.table$Za)] <- 0
  res.table$Z <- res.table$Z  * sign(res.table$log2FoldChange)
  res.table$Za <- res.table$Za  * sign(res.table$log2FoldChange)
  res.table=res.table[order(res.table$Z),]
  if (!is.null(dec)){
    res.table=res.table[order(res.table$Z,decreasing=dec),]
  } else { # otherwise sort by absolute magnitude of Z
    res.table <- res.table[order(abs(res.table$Z),decreasing=T),]
  }
  
  return(res.table)
}
listToOverlapMatrix2=function(res,counts=NULL){
  
  
  lc=length(res)
  stat=matrix(rep(0,lc*lc),lc,lc)
  for( i in seq(lc)){
    for (j in seq(lc)){
      if( i!=j){
        stat[i,j]=length(intersect(res[[i]],res[[j]]))/length(union(res[[i]],res[[j]]))
        if (!is.null(counts)){stat[i,j]=length(intersect(res[[i]],res[[j]])) }
      }
    }
  }
  colnames(stat)=names(res)
  rownames(stat)=names(res)
  return(stat)
  
}
rr <- drawOverlapHetmap(del$IB,dec=NULL,n.top.genes=300)
pheatmap(rr,na_col = "gray50",filename='de.IB.overlap.pdf',height=4.3,width=5,treeheight_row=25,treeheight_col=25)
pheatmap(rr,na_col = "gray50",treeheight_row=25,treeheight_col=25)

Compare pared and regular tests

x <- del$ID; y <- del2$pID;
x <- del$TD; y <- del2$pTD; 
x <- x[unlist(lapply(x,is.list))]
y <- y[unlist(lapply(y,is.list))]
sum(unlist(lapply(x,function(x) sum(x$res$pvalue<1e-3,na.rm=T))))
[1] 2576
sum(unlist(lapply(y,function(x) sum(x$res$pvalue<1e-3,na.rm=T))))
[1] 2355

Intra-tumoral heterogeneity

scon <- Conos$new(readRDS("~pkharchenko/m/scadden/bmmet/jan2019/scon.rds"))
nfac <- readRDS("../nfac.rds")
#nfac <- readRDS("/d0-mendel/home/meisl/Workplace/BMME/Figures/data/cell.ano.merged.rds")


tumor.cells <- names(nfac)[nfac=='Tumor']

tt <- table(scon$getDatasetPerCell()[tumor.cells])
valid.samples <- names(tt)[tt>10]

cdl <- lapply(scon$samples[valid.samples],function(x) t(x$misc$rawCounts[rownames(x$misc$rawCounts) %in% tumor.cells,,drop=F]))
names(cdl) <- gsub("Noninvolved","Distal",names(cdl))
cdl <- lapply(cdl,function(m) { colnames(m) <- gsub("Noninvolved","Distal",colnames(m)); m})

tp2 <- mclapply(cdl,basicP2proc,  nPcs=3, get.tsne=T, get.largevis=F, make.geneknn=F, trim=0 , min.cells.per.gene = -1, n.cores=1,n.odgenes=1000,mc.cores=30)

Conos

tcon <- Conos$new(tp2,n.cores=1)
tcon$buildGraph(k = 10,k.self = 5,ncomps = 10,metric = 'L2',n.odgenes = 1000)
found 0 out of 45 cached PCA  space pairs ... running 45 additional PCA  space pairs ............................................. done
inter-sample links using  mNN  ............................................. done
local pairs local pairs .......... done
building graph ..done
# store different embeddings
tcon$misc$embeddings <- list()
#tcon$embedGraph(method='UMAP');
#tcon$misc$embeddings$umap <- tcon$embedding
tcon$embedGraph(sgd_batches=0.1e8,apha=1);
Estimating embeddings.
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
tcon$misc$embeddings$lv <- tcon$embedding

tcon$findCommunities(method=leiden.community,resolution=1)
alpha <- 0.3; size <- 2; 
tcon$embedding <- tcon$misc$embeddings$lv
n1 <- tcon$plotGraph(color.by='sample',alpha=alpha,size=size,mark.groups=T,plot.na=F)
#n2 <- tcon$plotGraph(gene='FOS',alpha=alpha,size=size,mark.groups=F,plot.na=F)
n2 <- tcon$plotGraph(alpha=alpha,size=size,mark.groups=T,plot.na=F)
plot_grid(plotlist=list(n1,n2),nrow=1)

tcon$plotGraph(colors=csm[,4],alpha=alpha,size=size,mark.groups=F,plot.na=F,gradient.range.quantile=0.97)
alpha <- 0.3; size <- 2; 
tcon$embedding <- tcon$misc$embeddings$lv
n1 <- tcon$plotGraph(color.by='sample',alpha=alpha,size=size,mark.groups=F,plot.na=F)+theme(legend.position = 'right')+ guides(color=guide_legend(title="Sample",override.aes=list(size=5)))
ggsave('tcon.samples.pdf',n1,width=4.5,height=3)
n1

cm <- do.call(cbind,lapply(conos:::rawMatricesWithCommonGenes(Conos$new(tp2)),function(m) t(m[rownames(m)%in% tumor.cells,,drop=F])))
tcp2 <- basicP2proc(cm,  nPcs=20, get.tsne=T, get.largevis=F, make.geneknn=F, trim=1 , min.cells.per.gene = 0, n.cores=30,n.odgenes=3000)
tcp2$plotEmbedding(type='PCA',groups=as.factor(scon$getDatasetPerCell()))
using provided groups as a factor

conos::embeddingPlot(tcp2$embeddings[[1]]$tSNE,groups=as.factor(scon$getDatasetPerCell()),size=2)

conos::embeddingPlot(tcp2$embeddings[[1]]$tSNE,colors=csm[,4],gradient.range.quantile=0.97)
# epithelial cell differentiation
# ELF3
conos::embeddingPlot(tcp2$embeddings[[1]]$tSNE,colors=tcp2$counts[,'SOX9'],gradient.range.quantile=0.97)

Project other datasets to the PCs of each dataset

common.genes <- Reduce(intersect, lapply(tp2,function(x) colnames(x$counts)))

n.top.pcs <- 1;
pcl <- lapply(tp2,function(r) { 
  x <- r$misc$PCA$v[,1:n.top.pcs,drop=F]
  x <- x[rownames(x) %in% common.genes,,drop=F]
  x <- t(t(x)/colSums(x*x))
})

# project
csm <- do.call(rbind,lapply(tp2,function(r) {
  x <- r$counts;
  #x@x <- x@x*rep(r$misc[['varinfo']][colnames(x),'gsf'],diff(x@p)); # apply variance scaling
  center <- Matrix::colMeans(x)
  pcs <- do.call(cbind,lapply(pcl,function(pcm) {
    #z <- pcm[sample(1:nrow(pcm)),,drop=F]; rownames(z) <- rownames(pcm); pcm <- z;
    pcas <- t(as.matrix(t(x[,rownames(pcm),drop=F] %*% pcm)) - as.vector(t(center[rownames(pcm)] %*% pcm)))
  }))
  if(n.top.pcs>1) { colnames(pcs) <- paste(rep(names(pcl),each=n.top.pcs),rep(1:n.top.pcs,length(pcl)),sep='_')} else { colnames(pcs) <- names(pcl)}
  
  pcs
}))

Optimize orientations:


for(i in 1:10) {
   cc <- cor(csm)
   diag(cc) <- 0;
   
   mi <- sort(apply(cc,1,function(x) x[which.max(abs(x))]))
   if(mi[1]<0) {
     flip.ind <- names(mi)[1]
     csm[,flip.ind] <- -1*csm[,flip.ind]
   }
}

Visualize

Scores

require(ComplexHeatmap)
sl <- unique(gsub("-.*","",colnames(csm))); sc <- setNames(rainbow(length(sl)),sl)
hc <- hclust(as.dist(1-abs(cor(csm,method='spearman'))))
#hc <- hclust(as.dist(1-cor(csm)))
plot(hc)

co <- hc$order

Heatmap(csm, cluster_rows = T, show_row_names = F,border=T, column_order=co,
        bottom_annotation=HeatmapAnnotation(sample=factor(setNames(gsub("-.*","",colnames(csm)),colnames(csm)),levels=sl),col=list(sample=sc)),
        right_annotation = HeatmapAnnotation(which='row',sample=factor(setNames(gsub("-.*","",rownames(csm)),rownames(csm)),levels=sl),col=list(sample=sc))
        )

Fix remaining flips

csm[,co[c(2,3)]] <- -csm[,co[c(2,3)]]
csm[,co[c(7:10)]] <- -csm[,co[c(7:10)]]
csm[,co[c(1)]] <- -csm[,co[c(1)]]
#x <- csm;
#depth <- Matrix::colSums(cm)[rownames(csm)];
#depth <- log10(depth); depth <- (depth-mean(depth))*range(diff(depth))*2
#csm <- cbind(csm,depth=depth)

hm <- Heatmap(csm, name='PC scores', cluster_rows = T, show_row_names = F,border=T, cluster_columns = hc,
        #bottom_annotation=HeatmapAnnotation(sample=factor(setNames(gsub("-.*","",colnames(csm)),colnames(csm)),levels=sl),col=list(sample=sc)),
        #right_annotation = HeatmapAnnotation(which='row',sample=factor(setNames(gsub("-.*","",rownames(csm)),rownames(csm)),levels=sl),col=list(sample=sc)),
        col=circlize::colorRamp2(c(-3, 0, 3), c('darkgreen','grey90','orange')),
        row_split = as.factor(setNames(gsub("_.*","",rownames(csm)),rownames(csm))),row_title_rot=0,
        use_raster = T,raster_device = "CairoPNG")

pdf(file='pc.heatmap.pdf',width=5,height=7); print(hm); dev.off()
null device 
          1 
hm

alpha <- 0.3; size <- 2;
pl <- apply(csm,2,function(d) tcon$plotGraph(colors=d,alpha=alpha,size=size,mark.groups=F,plot.na=F,gradient.range.quantile=0.95,palette=colorRampPalette(c('darkgreen','grey90','orange'),space='Lab'))) %>% 
    mapply(function(x,y) x+ggtitle(y)+ theme(plot.title = element_text(size=16)),.,colnames(csm), SIMPLIFY=F)
#pl <- mapply(function(x,y) { x+ggtitle(y)},pl,colnames(csm),SIMPLIFY=F)
pl <- plot_grid(plotlist=pl[co],nrow=2)
pdf(file='tcon.PCs.pdf',width=10,height=4.4); print(pl); dev.off();
png 
  2 
pl

alpha <- 0.8; size <- 4;
pl <- tcon$plotPanel(colors=csm[,'BMET10-Tumor'],palette=colorRampPalette(c('darkgreen','grey90','orange'),space='Lab'),size=size,alpha=alpha,gradient.range.quantile=0.95,nrow=2,title.size=0,return.plotlist=T)
pl <- mapply(function(x,y) x+ggtitle(y)+ theme(plot.title = element_text(size=16)),pl,names(tcon$samples),SIMPLIFY=F);
pp <- plot_grid(plotlist=pl[co],nrow=2)
pdf(file='tSNE.BMET10_PC.pdf',width=10,height=4.4); print(pp); dev.off();
png 
  2 
pp

alpha <- 0.8; size <- 2;
#sig1 IER2, SOX9, SOX4, FOS? JUNB   BRD2, AHNAK
#sig2 MT-CO2, MT-CO1
tcon$plotPanel(gene='UBE2T',size=size,alpha=alpha,gradient.range.quantile=0.95,nrow=2,title.size=4)

Expression on the sample-specific tSNEs

gns <- c("IER2","JUNB",'SOX4','STMN1','H2AFZ','UBE2T')
gns <- c("IER2",'SOX4','UBE2T')
alpha <- 0.8; size <- 3
pl <- lapply(gns,function(g) { 
  pl <- tcon$plotPanel(gene=g,size=size,alpha=alpha,gradient.range.quantile=0.95,nrow=1,title.size=0,return.plotlist=T)[co];
  #pl <- mapply(function(x,y) x+ggtitle(y)+ theme(plot.title = element_text(size=16)),pl,names(tcon$samples),SIMPLIFY=F);
})

pp <- plot_grid(plotlist=unlist(pl,recursive=F),nrow=length(gns))
pdf(file='tSNE.genes.pdf',width=20,height=6); print(pp); dev.off();
png 
  2 
pp

PC overlap

listToOverlapMatrix2=function(res,counts=NULL){

  lc=length(res)
  stat=matrix(rep(0,lc*lc),lc,lc)
  for( i in seq(lc)){
    for (j in seq(lc)){
      if( i!=j){
        stat[i,j]=length(intersect(res[[i]],res[[j]]))/length(union(res[[i]],res[[j]]))
        if (!is.null(counts)){stat[i,j]=length(intersect(res[[i]],res[[j]])) }
      }
    }
  }
  colnames(stat)=names(res)
  rownames(stat)=names(res)
  return(stat)
  
}
topgnl <- lapply(pcl,function(x) na.omit((rownames(x)[order(abs(x[,1]),decreasing =T)])[1:200]))
om <- listToOverlapMatrix2(topgnl); diag(om) <- NA;
#pheatmap(rr,na_col = "gray50",filename='de.IB.overlap.pdf',height=4.3,width=5,treeheight_row=25,treeheight_col=25)
hm <- pheatmap::pheatmap(om,na_col = "gray50",treeheight_row=25,treeheight_col=25)
pdf(file='overlap.heatmap.pdf',width=6,height=5); print(hm); dev.off();
hm
intersect(topgnl[['BMET1-Tumor']],topgnl[['BMET5-Tumor']])
intersect(topgnl[['BMET10-Tumor']],topgnl[['BMET5-Tumor']])

Calculate go enrichments

topgnl <- lapply(pcl,function(x) (rownames(x)[order(abs(x[,1]),decreasing =T)])[1:200])
topgnl$intersect <- intersect(topgnl[['BMET10-Tumor']],topgnl[['BMET5-Tumor']])
#topgnl <- unlist(lapply(1:ncol(pcl[[1]]),function(i) setNames(lapply(pcl,function(x) (rownames(x)[order(abs(x[,i]),decreasing =T)])[1:200]) ,paste(names(pcl),i,sep='_'))),recursive=F)
#topgnl$intersect <- intersect(topgnl[['BMET10-Tumor_1']],topgnl[['BMET5-Tumor_1']])

topgnl$all <- common.genes;
topgnl.entrez <- lapply(topgnl, bitr, 'SYMBOL', 'ENTREZID', org.Hs.eg.db) %>% lapply(`[[`, "ENTREZID")
common.genes.entrez <- topgnl.entrez$all; topgnl.entrez$all <- NULL;

top.gos <- lapply(topgnl.entrez,enrichGOOpt,universe=common.genes.entrez, ont='BP', goData=go_datas[['BP']], readable=T, OrgDB=org.Hs.eg.db)
cx <-gos.cluster(top.gos[-grep('intersect',names(top.gos))],n.clusters=10,max.pval=0.05)
mu <- Heatmap(as.matrix(cx$summary),col=cols$up,border=T,show_row_dend=F,show_column_dend=F, heatmap_legend_param = list(title = 'Z score'), row_names_max_width = unit(8, "cm"),row_names_gp = gpar(fontsize = 10),)
pdf(file='tumor.pc.functions.pdf',width=7,height=4.5); draw(mu, heatmap_legend_side = "left"); dev.off();
mu
names(cx$clusters)[cx$clusters==cx$cluster['epithelial cell differentiation']]
  [1] "mRNA processing"                                                                                
  [2] "RNA splicing"                                                                                   
  [3] "RNA splicing, via transesterification reactions"                                                
  [4] "RNA splicing, via transesterification reactions with bulged adenosine as nucleophile"           
  [5] "mRNA splicing, via spliceosome"                                                                 
  [6] "epithelial cell differentiation"                                                                
  [7] "response to starvation"                                                                         
  [8] "cellular response to starvation"                                                                
  [9] "positive regulation of programmed cell death"                                                   
 [10] "response to extracellular stimulus"                                                             
 [11] "cellular response to extracellular stimulus"                                                    
 [12] "response to nutrient levels"                                                                    
 [13] "negative regulation of cell proliferation"                                                      
 [14] "cellular response to nutrient levels"                                                           
 [15] "response to glucocorticoid"                                                                     
 [16] "positive regulation of apoptotic process"                                                       
 [17] "response to steroid hormone"                                                                    
 [18] "cellular response to hormone stimulus"                                                          
 [19] "regulation of epithelial cell proliferation"                                                    
 [20] "epithelial cell proliferation"                                                                  
 [21] "cellular response to external stimulus"                                                         
 [22] "type I interferon signaling pathway"                                                            
 [23] "cellular response to type I interferon"                                                         
 [24] "fat cell differentiation"                                                                       
 [25] "response to corticosteroid"                                                                     
 [26] "muscle structure development"                                                                   
 [27] "response to type I interferon"                                                                  
 [28] "negative regulation of multi-organism process"                                                  
 [29] "response to oxidative stress"                                                                   
 [30] "cellular response to oxidative stress"                                                          
 [31] "regulation of fat cell differentiation"                                                         
 [32] "response to cAMP"                                                                               
 [33] "ATF6-mediated unfolded protein response"                                                        
 [34] "response to growth factor"                                                                      
 [35] "female pregnancy"                                                                               
 [36] "head development"                                                                               
 [37] "cellular response to metal ion"                                                                 
 [38] "maintenance of protein localization in endoplasmic reticulum"                                   
 [39] "cellular response to lipid"                                                                     
 [40] "alpha-linolenic acid metabolic process"                                                         
 [41] "negative regulation of transcription from RNA polymerase II promoter in response to stress"     
 [42] "response to toxic substance"                                                                    
 [43] "response to purine-containing compound"                                                         
 [44] "cellular response to growth factor stimulus"                                                    
 [45] "cellular response to calcium ion"                                                               
 [46] "response to inorganic substance"                                                                
 [47] "multi-multicellular organism process"                                                           
 [48] "response to ketone"                                                                             
 [49] "regulation of cellular amide metabolic process"                                                 
 [50] "response to antibiotic"                                                                         
 [51] "regulation of translation"                                                                      
 [52] "regulation of transcription from RNA polymerase II promoter in response to stress"              
 [53] "cellular response to organic cyclic compound"                                                   
 [54] "response to mechanical stimulus"                                                                
 [55] "regulation of DNA-templated transcription in response to stress"                                
 [56] "cellular response to inorganic substance"                                                       
 [57] "brain development"                                                                              
 [58] "aging"                                                                                          
 [59] "negative regulation of translation"                                                             
 [60] "response to mineralocorticoid"                                                                  
 [61] "cellular response to antibiotic"                                                                
 [62] "cellular response to glucose starvation"                                                        
 [63] "response to organophosphorus"                                                                   
 [64] "acylglycerol homeostasis"                                                                       
 [65] "triglyceride homeostasis"                                                                       
 [66] "cellular response to transforming growth factor beta stimulus"                                  
 [67] "carboxylic acid biosynthetic process"                                                           
 [68] "maintenance of protein localization in organelle"                                               
 [69] "organic acid biosynthetic process"                                                              
 [70] "response to transforming growth factor beta"                                                    
 [71] "negative regulation of cellular amide metabolic process"                                        
 [72] "muscle tissue development"                                                                      
 [73] "response to calcium ion"                                                                        
 [74] "regulation of transmembrane receptor protein serine/threonine kinase signaling pathway"         
 [75] "cellular response to glucocorticoid stimulus"                                                   
 [76] "response to peptide"                                                                            
 [77] "transmembrane receptor protein serine/threonine kinase signaling pathway"                       
 [78] "cellular response to amino acid starvation"                                                     
 [79] "positive regulation of neuron death"                                                            
 [80] "regulation of response to oxidative stress"                                                     
 [81] "response to wounding"                                                                           
 [82] "antigen processing and presentation of exogenous peptide antigen via MHC class I, TAP-dependent"
 [83] "cell-cell adhesion"                                                                             
 [84] "cellular response to corticosteroid stimulus"                                                   
 [85] "positive regulation of long-term synaptic potentiation"                                         
 [86] "metal ion homeostasis"                                                                          
 [87] "regulation of systemic arterial blood pressure"                                                 
 [88] "cellular divalent inorganic cation homeostasis"                                                 
 [89] "ion homeostasis"                                                                                
 [90] "muscle cell differentiation"                                                                    
 [91] "transition metal ion homeostasis"                                                               
 [92] "divalent inorganic cation homeostasis"                                                          
 [93] "Fc receptor signaling pathway"                                                                  
 [94] "ephrin receptor signaling pathway"                                                              
 [95] "cation homeostasis"                                                                             
 [96] "inorganic ion homeostasis"                                                                      
 [97] "cellular metal ion homeostasis"                                                                 
 [98] "antimicrobial humoral immune response mediated by antimicrobial peptide"                        
 [99] "regulation of long-term synaptic potentiation"                                                  
[100] "striated muscle cell differentiation"                                                           
[101] "antimicrobial humoral response"                                                                 
[102] "regulation of translational initiation"                                                         
[103] "striated muscle tissue development"                                                             
[104] "transforming growth factor beta receptor signaling pathway"                                     
[105] "response to peptide hormone"                                                                    
[106] "muscle organ development"                                                                       
[107] "response to progesterone"                                                                       
[108] "response to oxygen levels"                                                                      
[109] "response to hydrogen peroxide"                                                                  
[110] "forebrain development"                                                                          
[111] "response to hypoxia"                                                                            
[112] "response to decreased oxygen levels"                                                            
[113] "negative regulation of fat cell differentiation"                                                
[114] "skeletal muscle cell differentiation"                                                           
[115] "cholesterol homeostasis"                                                                        
[116] "sterol homeostasis"                                                                             
[117] "protein kinase B signaling"                                                                     
[118] "skeletal muscle tissue development"                                                             
[119] "response to metal ion"                                                                          
[120] "female sex differentiation"                                                                     
[121] "skeletal muscle organ development"                                                              
[122] "detoxification of copper ion"                                                                   
[123] "stress response to copper ion"                                                                  
[124] "detoxification of inorganic compound"                                                           
[125] "stress response to metal ion"                                                                   
[126] "cellular response to copper ion"                                                                
[127] "cellular response to zinc ion"                                                                  
cx$joint[cx$joint$Description=='epithelial cell differentiation',]
cx$joint[cx$joint$Description=='transforming growth factor beta receptor signaling pathway',]
cx$joint[cx$joint$Description=='vasculature development',]
cx$joint[cx$joint$Description=="stress response to metal ion",]

Clustering-based analysis

tcon$findCommunities(method=leiden.community,resolution=0.6)
alpha <- 0.3; size <- 2; 
tcon$embedding <- tcon$misc$embeddings$lv
n1 <- tcon$plotGraph(color.by='sample',alpha=alpha,size=size,mark.groups=T,plot.na=F)
#n2 <- tcon$plotGraph(gene='FOS',alpha=alpha,size=size,mark.groups=F,plot.na=F)
n2 <- tcon$plotGraph(alpha=alpha,size=size,mark.groups=T,plot.na=F)
plot_grid(plotlist=list(n1,n2),nrow=1)

tfac <- tcon$clusters$leiden$groups
levels(tfac) <- c('1','2','3','3')
tcon.de <- tcon$getDifferentialGenes(groups=tfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)

Calculate enrichments

tgos <- calculate.gos(lapply(tcon.de,function(de) { de$pvalue <- de$PValue; de$padj <- de$PAdj; de$log2FoldChange <- de$M; rownames(de) <- de$Gene; list(res=de) }),n.top.genes = 100)
'select()' returned 1:1 mapping between keys and columns
7% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
5% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
14.13% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
17% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
11% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
14.13% of input gene IDs are fail to map...'select()' returned 1:1 mapping between keys and columns
7.12% of input gene IDs are fail to map...

Calculate and plot clusters:

cols <- list(up=colorRamp2(c(0, 6), c("grey98", "red")),down=colorRamp2(c(0, 6), c("grey98", "blue")))
n.clusters <- 20; max.pval <- 0.05;
cx <- lapply(tgos,gos.cluster,n.clusters=n.clusters,max.pval=max.pval)

A small version of the hetmap, for the main figure

source("~/m/p2/conos/R/plot.R")
genes <- c('MS4A1','CD79A','CD79B','MZB1','VPREB3','SEC11C','IGLL5','GNLY','GZMB','LILRA4','AZU1','MPO','FCN1','IER3','C5AR1','IGJ','SOX4','STMN1','MYL9','HBD','GZMK','AR','KLK2')
pp <- plotDEheatmap(scon,nfac,annot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T, column.metadata.colors = list(clusters=typefc.pal), order.clusters = T, additional.genes = genes, labeled.gene.subset = genes, min.auc = 0.6,use_raster = T,raster_device = "CairoPNG")
pp
LS0tCnRpdGxlOiAiR08gaGVhdG1hcHMgYW5kIHN1Y2giCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KClN3aXRjaCB0byBiaW9jb25kdWN0b3IgbW9kZQpgYGB7cn0KbGlicmFyeShwYWdvZGEyKQpsaWJyYXJ5KGNvbm9zKQpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGJhcHBseSkKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dyYXN0cikKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KGdnYmVlc3dhcm0pCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoY2x1c3RlclByb2ZpbGVyKQpsaWJyYXJ5KERPU0UpCmxpYnJhcnkob3JnLkhzLmVnLmRiKQpsaWJyYXJ5KGVucmljaHBsb3QpCgpyZXF1aXJlKENvbXBsZXhIZWF0bWFwKQpyZXF1aXJlKGNpcmNsaXplKQpgYGAKCgpMb2FkIGRlIHJlc3VsdHM6CmBgYHtyfQp4IDwtIGxpc3QuZmlsZXMocGF0aD0nLi4nLHBhdHRlcm49J2RlLioucmRzJyk7IG5hbWVzKHgpIDwtIGdzdWIoImRlLiguKikucmRzIiwiXFwxIix4KQpkZWwgPC0gbGFwcGx5KHgsZnVuY3Rpb24obikgcmVhZFJEUyhwYXN0ZTAoJy4uLycsbikpKQpgYGAKCgoKUHJlcGFyZSBHTyBjYXRlZ29yaWVzIC4uLiAKYGBge3J9CiBnb19kYXRhcyA8LSBjKCJCUCIsICJDQyIsICJNRiIpICU+JSBzZXROYW1lcyguLCAuKSAlPiUKICAgcGJsYXBwbHkoZnVuY3Rpb24obikgY2x1c3RlclByb2ZpbGVyOjo6Z2V0X0dPX2RhdGEob3JnLkhzLmVnLmRiLCBuLCAiRU5UUkVaSUQiKSAlPiUKICAgICAgICAgICAgICBhcy5saXN0KCkgJT4lIGFzLmVudmlyb25tZW50KCkpICMgb3RoZXJ3aXNlIGl0IHBhc3MgcmVmZXJlbmNlIHRvIHRoZSBlbnZpcm9ubWVudCBjb250ZW50CmBgYAoKUnVuIGVucmljaG1lbnQgdGVzdHMKCmBgYHtyfQplbnJpY2hHT09wdCA8LSBmdW5jdGlvbiAoZ2VuZSwgT3JnREIsIGdvRGF0YSwga2V5VHlwZSA9ICJFTlRSRVpJRCIsIG9udCA9ICJNRiIsIHB2YWx1ZUN1dG9mZiA9IDAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwgdW5pdmVyc2U9TlVMTCwgcXZhbHVlQ3V0b2ZmID0gMC4yLCBtaW5HU1NpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgIG1heEdTU2l6ZSA9IDUwMCwgcmVhZGFibGUgPSBGQUxTRSwgcG9vbCA9IEZBTFNFKSB7CiAgb250ICU8PiUgdG91cHBlciAlPiUgbWF0Y2guYXJnKGMoIkJQIiwgIkNDIiwgIk1GIikpCgogIHJlcyA8LSBjbHVzdGVyUHJvZmlsZXI6OjplbnJpY2hlcl9pbnRlcm5hbChnZW5lLCBwdmFsdWVDdXRvZmYgPSBwdmFsdWVDdXRvZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBBZGp1c3RNZXRob2QgPSBwQWRqdXN0TWV0aG9kLCB1bml2ZXJzZSA9IHVuaXZlcnNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdmFsdWVDdXRvZmYgPSBxdmFsdWVDdXRvZmYsIG1pbkdTU2l6ZSA9IG1pbkdTU2l6ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4R1NTaXplID0gbWF4R1NTaXplLCBVU0VSX0RBVEEgPSBnb0RhdGEpCiAgaWYgKGlzLm51bGwocmVzKSkKICAgIHJldHVybihyZXMpCgogIHJlc0BrZXl0eXBlIDwtIGtleVR5cGUKICByZXNAb3JnYW5pc20gPC0gY2x1c3RlclByb2ZpbGVyOjo6Z2V0X29yZ2FuaXNtKE9yZ0RCKQogIGlmIChyZWFkYWJsZSkgewogICAgcmVzIDwtIERPU0U6OnNldFJlYWRhYmxlKHJlcywgT3JnREIpCiAgfQogIHJlc0BvbnRvbG9neSA8LSBvbnQKCiAgcmV0dXJuKHJlcykKfQoKZGlzdGFuY2VCZXR3ZWVuVGVybXMgPC0gZnVuY3Rpb24oZ28uZGYpIHsKICBnZW5lcy5wZXIuZ28gPC0gc2FwcGx5KGdvLmRmJGdlbmVJRCwgc3Ryc3BsaXQsICIvIikgJT4lIHNldE5hbWVzKGdvLmRmJERlc2NyaXB0aW9uKQogIGFsbC5nby5nZW5lcyA8LSB1bmlxdWUodW5saXN0KGdlbmVzLnBlci5nbykpCiAgYWxsLmdvcyA8LSB1bmlxdWUoZ28uZGYkRGVzY3JpcHRpb24pCgogIGdlbmVzLnBlci5nby5tYXQgPC0gbWF0cml4KDAsIGxlbmd0aChhbGwuZ28uZ2VuZXMpLCBsZW5ndGgoYWxsLmdvcykpICU+JQogICAgYGNvbG5hbWVzPC1gKGFsbC5nb3MpICU+JSBgcm93bmFtZXM8LWAoYWxsLmdvLmdlbmVzKQoKICBmb3IgKGkgaW4gMTpsZW5ndGgoZ2VuZXMucGVyLmdvKSkgewogICAgZ2VuZXMucGVyLmdvLm1hdFtnZW5lcy5wZXIuZ29bW2ldXSwgZ28uZGYkRGVzY3JpcHRpb25bW2ldXV0gPC0gMQogIH0KCiAgcmV0dXJuKGRpc3QodChnZW5lcy5wZXIuZ28ubWF0KSwgbWV0aG9kPSJiaW5hcnkiKSkKfQoKYGBgCgoKYGBge3J9CmNhbGN1bGF0ZS5nb3MgPC0gZnVuY3Rpb24oZGUsbi50b3AuZ2VuZXM9MzAwLG4uY29yZXM9MSkgewogIGRlIDwtIGRlW3VubGlzdChsYXBwbHkoZGUsaXMubGlzdCkpXQogIAogICMgYWRkIFogc2NvcmVzCiAgZGUgPC0gbGFwcGx5KGRlLGZ1bmN0aW9uKGQpIHsKICAgIHJlcy50YWJsZSA8LSBkJHJlczsKICAgIHJlcy50YWJsZSRaIDwtIC1xbm9ybShyZXMudGFibGUkcHZhbC8yKQogICAgcmVzLnRhYmxlJFpbaXMubmEocmVzLnRhYmxlJFopXSA8LSAwCiAgICByZXMudGFibGUkWmEgPC0gLXFub3JtKHJlcy50YWJsZSRwYWRqLzIpCiAgICByZXMudGFibGUkWmFbaXMubmEocmVzLnRhYmxlJFphKV0gPC0gMAogICAgcmVzLnRhYmxlJFogPC0gcmVzLnRhYmxlJFogICogc2lnbihyZXMudGFibGUkbG9nMkZvbGRDaGFuZ2UpCiAgICByZXMudGFibGUkWmEgPC0gcmVzLnRhYmxlJFphICAqIHNpZ24ocmVzLnRhYmxlJGxvZzJGb2xkQ2hhbmdlKQogICAgZCRyZXMgPC0gcmVzLnRhYmxlOwogICAgZAogIH0pCiAgCiAgCiAgZ25zIDwtIGxpc3QoZG93bj1sYXBwbHkoZGUsZnVuY3Rpb24oeCkgcm93bmFtZXMoeCRyZXMpW29yZGVyKHgkcmVzJFosZGVjcmVhc2luZz1GKVsxOm4udG9wLmdlbmVzXV0pLAogICAgICAgICAgICAgIHVwPWxhcHBseShkZSxmdW5jdGlvbih4KSByb3duYW1lcyh4JHJlcylbb3JkZXIoeCRyZXMkWixkZWNyZWFzaW5nPVQpWzE6bi50b3AuZ2VuZXNdXSksCiAgICAgICAgICAgICAgYWxsPWxpc3QoYWxsPXVuaXF1ZSh1bmxpc3QobGFwcGx5KGRlLGZ1bmN0aW9uKHgpIHJvd25hbWVzKHgkcmVzKSkpKSkpCiAgCiAgZ25zLmVudHJleiA8LSBsYXBwbHkoZ25zLGZ1bmN0aW9uKHgpIGxhcHBseSh4LCBiaXRyLCAnU1lNQk9MJywgJ0VOVFJFWklEJywgb3JnLkhzLmVnLmRiKSAlPiUgbGFwcGx5KGBbW2AsICJFTlRSRVpJRCIpKQogIAogIGdvcyA8LSBsYXBwbHkoZ25zLmVudHJleltjKCd1cCcsJ2Rvd24nKV0sZnVuY3Rpb24oZ25zKSB7CiAgICBsYXBwbHkoZ25zLGVucmljaEdPT3B0LHVuaXZlcnNlPWducy5lbnRyZXokYWxsJGFsbCwgb250PSdCUCcsIGdvRGF0YT1nb19kYXRhc1tbJ0JQJ11dLCByZWFkYWJsZT1ULCBPcmdEQj1vcmcuSHMuZWcuZGIpIyAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpIHhAcmVzdWx0KQogIH0pCiAgCn0KCmdvcy5jbHVzdGVyIDwtIGZ1bmN0aW9uKGdvcyxuLmNsdXN0ZXJzPTIwLG1heC5wdmFsPTAuMDUpIHsKICBnb3NfZmlsdCA8LSBsYXBwbHkoZ29zLGZ1bmN0aW9uKHgpIGZpbHRlcih4QHJlc3VsdCxwLmFkanVzdDxtYXgucHZhbCkpCiAgZ29zX2pvaW50IDwtIGRvLmNhbGwocmJpbmQsZ29zX2ZpbHQpCiAgCiAgZ29zX2pvaW50IDwtIGdvc19maWx0ICU+JSAuW3NhcHBseSguLCBucm93KSA+IDBdICU+JSBuYW1lcygpICU+JSBzZXROYW1lcyguLCAuKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKG4pIGNiaW5kKGdvc19maWx0W1tuXV0sVHlwZT1uKSkgJT4lIFJlZHVjZShyYmluZCwuKQogIGdvX2Rpc3QgPC0gZGlzdGFuY2VCZXR3ZWVuVGVybXMoZ29zX2pvaW50KQogIGNsdXN0cyA8LSBoY2x1c3QoZ29fZGlzdCxtZXRob2Q9J3dhcmQuRDInKSAlPiUgY3V0cmVlKG4uY2x1c3RlcnMpCiAgZ29zX3Blcl9jbHVzdCA8LSBzcGxpdChuYW1lcyhjbHVzdHMpLCBjbHVzdHMpCiAgbmdvc19wZXJfY2x1c3QgPC0gc2FwcGx5KGdvc19wZXJfY2x1c3QsIGxlbmd0aCkKICAjdGFibGUoY2x1c3RzKQogIAogIGdvc19wZXJfY2x1c3QgPC0gc3BsaXQobmFtZXMoY2x1c3RzKSwgY2x1c3RzKQogIGdvc19qb2ludCAlPD4lIG11dGF0ZShHT0NsdXN0PWNsdXN0c1tEZXNjcmlwdGlvbl0pCiAgbmFtZV9wZXJfY2x1c3QgPC0gZ29zX2pvaW50ICU+JSBncm91cF9ieShHT0NsdXN0LCBEZXNjcmlwdGlvbikgJT4lIHN1bW1hcmlzZShwdmFsdWU9ZXhwKG1lYW4obG9nKHB2YWx1ZSkpKSkgJT4lIAogICAgc3BsaXQoLiRHT0NsdXN0KSAlPiUgc2FwcGx5KGZ1bmN0aW9uKGRmKSBkZiREZXNjcmlwdGlvblt3aGljaC5taW4oZGYkcHZhbHVlKV0pCiAgZ29zX2pvaW50ICU8PiUgbXV0YXRlKEdPQ2x1c3ROYW1lPW5hbWVfcGVyX2NsdXN0W2FzLmNoYXJhY3RlcihHT0NsdXN0KV0pCiAgCiAgIyBjbHVzdGVyIHN1bW1hcnkKICBnb19icF9zdW1tX2RmIDwtIGdvc19qb2ludCAlPiUgZ3JvdXBfYnkoVHlwZSwgR09DbHVzdE5hbWUpICU+JSAKICAgIHN1bW1hcmlzZShwLmFkanVzdD1taW4ocC5hZGp1c3QpKSAlPiUgdW5ncm91cCgpICU+JSBtdXRhdGUocC5hZGp1c3Q9LWxvZzEwKHAuYWRqdXN0KSkgJT4lIAogICAgdGlkeXI6OnNwcmVhZChUeXBlLCBwLmFkanVzdCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgc2V0X3Jvd25hbWVzKC4kR09DbHVzdE5hbWUpICU+JSAuWywgMjpuY29sKC4pXSAjJT4lIC5bLCB0eXBlX29yZGVyW3R5cGVfb3JkZXIgJWluJSBjb2xuYW1lcyguKV1dCiAgZ29fYnBfc3VtbV9kZltpcy5uYShnb19icF9zdW1tX2RmKV0gPC0gMAogIAogIHJldHVybihsaXN0KGpvaW50PWdvc19qb2ludCxjbHVzdGVycz1jbHVzdHMsc3VtbWFyeT1nb19icF9zdW1tX2RmKSkKfQoKYGBgCgoKQ2FsY3VsYXRlIGVucmljaG1lbnRzCmBgYHtyfQpnb3NsIDwtIGxhcHBseShkZWwsY2FsY3VsYXRlLmdvcykKYGBgCgpDYWxjdWxhdGUgYW5kIHBsb3QgY2x1c3RlcnM6CmBgYHtyfQpjb2xzIDwtIGxpc3QodXA9Y29sb3JSYW1wMihjKDAsIDYpLCBjKCJncmV5OTgiLCAicmVkIikpLGRvd249Y29sb3JSYW1wMihjKDAsIDYpLCBjKCJncmV5OTgiLCAiYmx1ZSIpKSkKbi5jbHVzdGVycyA8LSAyMDsgbWF4LnB2YWwgPC0gMC4wNTsKYGBgCgojIyMgSW52b2x2ZWQgdnMuIEJlbmlnbgpgYGB7cn0KY3ggPC0gbGFwcGx5KGdvc2wkSUIsZ29zLmNsdXN0ZXIsbi5jbHVzdGVycz1uLmNsdXN0ZXJzLG1heC5wdmFsPW1heC5wdmFsKQpgYGAKCmBgYHtyIGZpZy53aWR0aD04LGZpZy5oZWlnaHQ9NX0KbXUgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkdXAkc3VtbWFyeSksY29sPWNvbHMkdXAsYm9yZGVyPVQsc2hvd19yb3dfZGVuZD1GLHNob3dfY29sdW1uX2RlbmQ9RiwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ1ogc2NvcmUnKSwgcm93X25hbWVzX21heF93aWR0aCA9IHVuaXQoOCwgImNtIikscm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSwpCm1kIDwtIEhlYXRtYXAoYXMubWF0cml4KGN4JGRvd24kc3VtbWFyeSksY29sPWNvbHMkZG93bixib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApKQpwZGYoZmlsZT0nZGUuSUIudXAucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG11LCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwpwZGYoZmlsZT0nZGUuSUIuZG93bi5wZGYnLHdpZHRoPTcsaGVpZ2h0PTQuNSk7IGRyYXcobWQsIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAibGVmdCIpOyBkZXYub2ZmKCk7Cm11OyBtZDsKYGBgCgoKRmlndXJlIG91dHB1dCB3aXRoIGZpeGVkIGNlbGwgdHlwZSBvcmRlcmluZwoKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD01fQpvcmQgPC0gaGNsdXN0KGRpc3QodChyYmluZChjeCR1cCRzdW1tYXJ5LGN4JGRvd24kc3VtbWFyeSkpKSkkb3JkZXI7Cm11IDwtIEhlYXRtYXAoYXMubWF0cml4KGN4JHVwJHN1bW1hcnlbLG9yZF0pLGNvbD1jb2xzJHVwLGJvcmRlcj1ULHNob3dfcm93X2RlbmQ9RixzaG93X2NvbHVtbl9kZW5kPUYsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICdaIHNjb3JlJyksIHJvd19uYW1lc19tYXhfd2lkdGggPSB1bml0KDgsICJjbSIpLHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxMCksY29sdW1uX25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDExKSxjbHVzdGVyX2NvbHVtbnM9RixzaG93X2hlYXRtYXBfbGVnZW5kID1GKQptZCA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCRkb3duJHN1bW1hcnlbLG9yZF0pLGNvbD1jb2xzJGRvd24sYm9yZGVyPVQsc2hvd19yb3dfZGVuZD1GLHNob3dfY29sdW1uX2RlbmQ9RiwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ1ogc2NvcmUnKSwgcm93X25hbWVzX21heF93aWR0aCA9IHVuaXQoOCwgImNtIikscm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSxjb2x1bW5fbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTEpLGNsdXN0ZXJfY29sdW1ucz1GLHNob3dfaGVhdG1hcF9sZWdlbmQgPUYpCnBkZihmaWxlPSdkZS5JQi51cC5wZGYnLHdpZHRoPTUuNSxoZWlnaHQ9NC41KTsgZHJhdyhtdSwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKcGRmKGZpbGU9J2RlLklCLmRvd24ucGRmJyx3aWR0aD01LjUsaGVpZ2h0PTQuNSk7IGRyYXcobWQsIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAibGVmdCIpOyBkZXYub2ZmKCk7Cm11OyBtZDsKYGBgCgoKIyMjIFR1bW9yIHZzLiBCZW5pZ24KYGBge3J9CmN4IDwtIGxhcHBseShnb3NsJFRCLGdvcy5jbHVzdGVyLG4uY2x1c3RlcnM9bi5jbHVzdGVycyxtYXgucHZhbD1tYXgucHZhbCkKYGBgCgpgYGB7ciBmaWcud2lkdGg9OCxmaWcuaGVpZ2h0PTV9Cm11IDwtIEhlYXRtYXAoYXMubWF0cml4KGN4JHVwJHN1bW1hcnkpLGNvbD1jb2xzJHVwLGJvcmRlcj1ULHNob3dfcm93X2RlbmQ9RixzaG93X2NvbHVtbl9kZW5kPUYsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICdaIHNjb3JlJyksIHJvd19uYW1lc19tYXhfd2lkdGggPSB1bml0KDgsICJjbSIpLHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxMCksKQptZCA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCRkb3duJHN1bW1hcnkpLGNvbD1jb2xzJGRvd24sYm9yZGVyPVQsc2hvd19yb3dfZGVuZD1GLHNob3dfY29sdW1uX2RlbmQ9RiwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ1ogc2NvcmUnKSwgcm93X25hbWVzX21heF93aWR0aCA9IHVuaXQoOCwgImNtIikscm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSkKcGRmKGZpbGU9J2RlLlRCLnVwLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtdSwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKcGRmKGZpbGU9J2RlLlRCLmRvd24ucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG1kLCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwptdTsgbWQ7CmBgYAoKCiMjIyBJbnZvbHZlZCB2cy4gRGlzdGFsCmBgYHtyfQpjeCA8LSBsYXBwbHkoZ29zbCRJRCxnb3MuY2x1c3RlcixuLmNsdXN0ZXJzPW4uY2x1c3RlcnMsbWF4LnB2YWw9bWF4LnB2YWwpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsZmlnLmhlaWdodD01fQptdSA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCR1cCRzdW1tYXJ5KSxjb2w9Y29scyR1cCxib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApLCkKbWQgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkZG93biRzdW1tYXJ5KSxjb2w9Y29scyRkb3duLGJvcmRlcj1ULHNob3dfcm93X2RlbmQ9RixzaG93X2NvbHVtbl9kZW5kPUYsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICdaIHNjb3JlJyksIHJvd19uYW1lc19tYXhfd2lkdGggPSB1bml0KDgsICJjbSIpLHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxMCkpCnBkZihmaWxlPSdkZS5JRC51cC5wZGYnLHdpZHRoPTcsaGVpZ2h0PTQuNSk7IGRyYXcobXUsIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAibGVmdCIpOyBkZXYub2ZmKCk7CnBkZihmaWxlPSdkZS5JRC5kb3duLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtZCwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKbXU7IG1kOwpgYGAKCgpUcnkgcGxvdHRpbmcgaW5kaXZpZHVhbCByZXN1bHRzOgpgYGB7cn0KeCA8LSBnb3NsMiRJQiR1cCRQcm9nZW5pdG9ycwpiYXJwbG90KHgsIHNob3dDYXRlZ29yeT0yMCkKYGBgCgoKYGBge3J9CnggPC0gZ29zbCRJQiR1cCRQcm9nZW5pdG9ycwpwIDwtIGRvdHBsb3QoeCwgc2hvd0NhdGVnb3J5PTIwLHRpdGxlPSdVcHJlZ3VsYXRlZCBpbiBQcm9nZW5pdG9ycycpICtzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gInJlZCIsIGhpZ2ggPSAiZ3JheTgwIikgK3hsYWIoJ2ZyYWN0aW9uIG9mIGdlbmVzJykgK3NjYWxlX3hfY29udGludW91cyhicmVha3M9cHJldHR5KHVubGlzdChsYXBwbHkoeEByZXN1bHQkR2VuZVJhdGlvWzE6MjBdLGZ1bmN0aW9uKHgpIGV2YWwocGFyc2UodGV4dD14KSkpKSwzKSkKZ2dzYXZlKCdJQi5Qcm9nZW5pdG9yLnVwLnBkZicsd2lkdGg9NixoZWlnaHQ9NixwbG90PXApCnAKYGBgCgoKYGBge3J9CnggPC0gZ29zbCRJQiRkb3duJFByb2dlbml0b3JzCnAgPC0gZG90cGxvdCh4LCBzaG93Q2F0ZWdvcnk9MjAsdGl0bGU9J0Rvd25yZWd1bGF0ZWQgaW4gUHJvZ2VuaXRvcnMnKSArc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgaGlnaCA9ICJncmF5ODAiKSt4bGFiKCdmcmFjdGlvbiBvZiBnZW5lcycpK3NjYWxlX3hfY29udGludW91cyhicmVha3M9cHJldHR5KHVubGlzdChsYXBwbHkoeEByZXN1bHQkR2VuZVJhdGlvWzE6MjBdLGZ1bmN0aW9uKHgpIGV2YWwocGFyc2UodGV4dD14KSkpKSwzKSkKZ2dzYXZlKCdJQi5Qcm9nZW5pdG9yLmRvd24ucGRmJyx3aWR0aD02LGhlaWdodD02LHBsb3Q9cCkKcApgYGAKCgpgYGB7cn0KeCA8LSBnb3NsMiRJQiR1cCRwREMKcCA8LSBkb3RwbG90KHgsIHNob3dDYXRlZ29yeT0yMCx0aXRsZT0nVXByZWd1bGF0ZWQgaW4gcERDJykgK3NjYWxlX2NvbG91cl9ncmFkaWVudChsb3cgPSAicmVkIiwgaGlnaCA9ICJncmF5ODAiKSAreGxhYignZnJhY3Rpb24gb2YgZ2VuZXMnKQpnZ3NhdmUoJ0lCLnBEQy51cC5wZGYnLHdpZHRoPTgsaGVpZ2h0PTYscGxvdD1wKQpwCmBgYAoKCmBgYHtyfQp4IDwtIGdvc2wyJElCJGRvd24kcERDCnAgPC0gZG90cGxvdCh4LCBzaG93Q2F0ZWdvcnk9MjAsdGl0bGU9J0Rvd25yZWd1bGF0ZWQgaW4gcERDJykgK3NjYWxlX2NvbG91cl9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIGhpZ2ggPSAiZ3JheTgwIikKZ2dzYXZlKCdJQi5wREMuZG93bi5wZGYnLHdpZHRoPTgsaGVpZ2h0PTYscGxvdD1wKQpwCmBgYAoKCgojIyMgQWRkaXRpb25hbCBjb21wYXJpc29ucyAocGFpcmVkLCBjb3JyZWN0ZWQpCkxvYWQgU2hlbmdsaW4ncyByZXN1bHRzCmBgYHtyfQpwdyA8LSAnL2hvbWUvbWVpc2wvV29ya3BsYWNlL0JNTUUvUmV2aXNpb24vREUvJwp4IDwtIGxpc3QoY1RCPSdkZS5jb3JyZWN0aW9uLlR1bW9yLnZzLkJlbmlnbi5yZHMnLHBJRD0nZGUucGFpcmVkLkludm9sdmVkLnZzLkRpc3RhbC5yZHMnLHBURD0nZGUucGFpcmVkLlR1bW9yLnZzLkRpc3RhbC5yZHMnKQpkZWwyIDwtIGxhcHBseSh4LGZ1bmN0aW9uKHgpIHJlYWRSRFMocGFzdGUwKHB3LHgpKSkKCmRlbDIkcElEIDwtIHJlYWRSRFMoIi4uL2RlLnBJRC5yZHMiKQpkZWwyJHBURCA8LSByZWFkUkRTKCIuLi9kZS5wVEQucmRzIikKCmdvc2wyIDwtIGxhcHBseShkZWwyLGNhbGN1bGF0ZS5nb3MpCmBgYAoKCgpDb3JyZWN0ZWQgVHVtb3IgdnMuIEJlbmlnbgpgYGB7cn0KY3ggPC0gbGFwcGx5KGdvc2wyJGNUQixnb3MuY2x1c3RlcixuLmNsdXN0ZXJzPW4uY2x1c3RlcnMsbWF4LnB2YWw9bWF4LnB2YWwpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsZmlnLmhlaWdodD01fQptdSA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCR1cCRzdW1tYXJ5KSxjb2w9Y29scyR1cCxib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApLCkKbWQgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkZG93biRzdW1tYXJ5KSxjb2w9Y29scyRkb3duLGJvcmRlcj1ULHNob3dfcm93X2RlbmQ9RixzaG93X2NvbHVtbl9kZW5kPUYsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICdaIHNjb3JlJyksIHJvd19uYW1lc19tYXhfd2lkdGggPSB1bml0KDgsICJjbSIpLHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxMCkpCnBkZihmaWxlPSdkZS5jVEIudXAucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG11LCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwpwZGYoZmlsZT0nZGUuY1RCLmRvd24ucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG1kLCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwptdTsgbWQ7CmBgYAoKUGFpcmVkIFR1bW9yIHZzLiBEaXN0YWwKYGBge3J9CmN4IDwtIGxhcHBseShnb3NsMiRwVEQsZ29zLmNsdXN0ZXIsbi5jbHVzdGVycz1uLmNsdXN0ZXJzLG1heC5wdmFsPW1heC5wdmFsKQpgYGAKCmBgYHtyIGZpZy53aWR0aD04LGZpZy5oZWlnaHQ9NX0KbXUgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkdXAkc3VtbWFyeSksY29sPWNvbHMkdXAsYm9yZGVyPVQsc2hvd19yb3dfZGVuZD1GLHNob3dfY29sdW1uX2RlbmQ9RiwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ1ogc2NvcmUnKSwgcm93X25hbWVzX21heF93aWR0aCA9IHVuaXQoOCwgImNtIikscm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSwpCm1kIDwtIEhlYXRtYXAoYXMubWF0cml4KGN4JGRvd24kc3VtbWFyeSksY29sPWNvbHMkZG93bixib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApKQpwZGYoZmlsZT0nZGUucFRELnVwLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtdSwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKcGRmKGZpbGU9J2RlLnBURC5kb3duLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtZCwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKbXU7IG1kOwpgYGAKCgpQYWlyZWQgSW52b2x2ZWQgdnMuIERpc3RhbApgYGB7cn0KY3ggPC0gbGFwcGx5KGdvc2wyJHBJRCxnb3MuY2x1c3RlcixuLmNsdXN0ZXJzPW4uY2x1c3RlcnMsbWF4LnB2YWw9bWF4LnB2YWwpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsZmlnLmhlaWdodD01fQptdSA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCR1cCRzdW1tYXJ5KSxjb2w9Y29scyR1cCxib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApLCkKbWQgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkZG93biRzdW1tYXJ5KSxjb2w9Y29scyRkb3duLGJvcmRlcj1ULHNob3dfcm93X2RlbmQ9RixzaG93X2NvbHVtbl9kZW5kPUYsIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICdaIHNjb3JlJyksIHJvd19uYW1lc19tYXhfd2lkdGggPSB1bml0KDgsICJjbSIpLHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxMCkpCnBkZihmaWxlPSdkZS5wSUQudXAucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG11LCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwpwZGYoZmlsZT0nZGUucElELmRvd24ucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG1kLCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwptdTsgbWQ7CmBgYAoKClJlZHJhdyBoZWF0bWFwcwpgYGB7cn0Kc24gPC0gZnVuY3Rpb24oeCkgc2V0TmFtZXMoeCx4KQpkcmF3T3ZlcmxhcEhldG1hcD1mdW5jdGlvbihEZWZpbGUsZGVjPU5VTEwsbi50b3AuZ2VuZXM9MzAwKXsKICAKICBubj1uYW1lcyhEZWZpbGUpCiAgc25hbWU9bm5bdW5saXN0KGxhcHBseShEZWZpbGUsIGZ1bmN0aW9uKHgpICdyZXMnICVpbiUgbmFtZXMoeCkpKV0KICAKICAKICBnbGlzdD0gbGFwcGx5KHNuKHNuYW1lKSwgZnVuY3Rpb24oeCl7IAogICAgdG1wMT1EZWZpbGVbW3hdXSRyZXMKICAgIHRtcDE9YWRkWnNjb3JlKHRtcDEsZGVjPWRlYykKICAgIHJvd25hbWVzKHRtcDEpWzE6bi50b3AuZ2VuZXNdCiAgfSkKICAKICBac2NvcmU9IGxhcHBseShzbihzbmFtZSksIGZ1bmN0aW9uKHgpeyAKICAgIHRtcDE9RGVmaWxlW1t4XV0kcmVzCiAgICB0bXAxPWFkZFpzY29yZSh0bXAxLGRlYz1kZWMpCiAgICBzcz10bXAxJFoKICAgIG5hbWVzKHNzKT1yb3duYW1lcyh0bXAxKQogICAgc3MKICB9KQogIAogIAogIAogIHJyPWxpc3RUb092ZXJsYXBNYXRyaXgyKGdsaXN0KQogIAogIGZvciggaSBpbiBzZXEobnJvdyhycikpKXsKICAgIHJyW2ksaV09TkEKICB9CiAgCiAgcnIKfQoKYWRkWnNjb3JlPWZ1bmN0aW9uKHJlcy50YWJsZSxkZWM9TlVMTCl7CiAgcmVzLnRhYmxlJFogPC0gLXFub3JtKHJlcy50YWJsZSRwdmFsLzIpCiAgcmVzLnRhYmxlJFpbaXMubmEocmVzLnRhYmxlJFopXSA8LSAwCiAgcmVzLnRhYmxlJFphIDwtIC1xbm9ybShyZXMudGFibGUkcGFkai8yKQogIHJlcy50YWJsZSRaYVtpcy5uYShyZXMudGFibGUkWmEpXSA8LSAwCiAgcmVzLnRhYmxlJFogPC0gcmVzLnRhYmxlJFogICogc2lnbihyZXMudGFibGUkbG9nMkZvbGRDaGFuZ2UpCiAgcmVzLnRhYmxlJFphIDwtIHJlcy50YWJsZSRaYSAgKiBzaWduKHJlcy50YWJsZSRsb2cyRm9sZENoYW5nZSkKICByZXMudGFibGU9cmVzLnRhYmxlW29yZGVyKHJlcy50YWJsZSRaKSxdCiAgaWYgKCFpcy5udWxsKGRlYykpewogICAgcmVzLnRhYmxlPXJlcy50YWJsZVtvcmRlcihyZXMudGFibGUkWixkZWNyZWFzaW5nPWRlYyksXQogIH0gZWxzZSB7ICMgb3RoZXJ3aXNlIHNvcnQgYnkgYWJzb2x1dGUgbWFnbml0dWRlIG9mIFoKICAgIHJlcy50YWJsZSA8LSByZXMudGFibGVbb3JkZXIoYWJzKHJlcy50YWJsZSRaKSxkZWNyZWFzaW5nPVQpLF0KICB9CiAgCiAgcmV0dXJuKHJlcy50YWJsZSkKfQpsaXN0VG9PdmVybGFwTWF0cml4Mj1mdW5jdGlvbihyZXMsY291bnRzPU5VTEwpewogIAogIAogIGxjPWxlbmd0aChyZXMpCiAgc3RhdD1tYXRyaXgocmVwKDAsbGMqbGMpLGxjLGxjKQogIGZvciggaSBpbiBzZXEobGMpKXsKICAgIGZvciAoaiBpbiBzZXEobGMpKXsKICAgICAgaWYoIGkhPWopewogICAgICAgIHN0YXRbaSxqXT1sZW5ndGgoaW50ZXJzZWN0KHJlc1tbaV1dLHJlc1tbal1dKSkvbGVuZ3RoKHVuaW9uKHJlc1tbaV1dLHJlc1tbal1dKSkKICAgICAgICBpZiAoIWlzLm51bGwoY291bnRzKSl7c3RhdFtpLGpdPWxlbmd0aChpbnRlcnNlY3QocmVzW1tpXV0scmVzW1tqXV0pKSB9CiAgICAgIH0KICAgIH0KICB9CiAgY29sbmFtZXMoc3RhdCk9bmFtZXMocmVzKQogIHJvd25hbWVzKHN0YXQpPW5hbWVzKHJlcykKICByZXR1cm4oc3RhdCkKICAKfQoKYGBgCgoKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD01fQpyciA8LSBkcmF3T3ZlcmxhcEhldG1hcChkZWwkSUIsZGVjPU5VTEwsbi50b3AuZ2VuZXM9MzAwKQpwaGVhdG1hcChycixuYV9jb2wgPSAiZ3JheTUwIixmaWxlbmFtZT0nZGUuSUIub3ZlcmxhcC5wZGYnLGhlaWdodD00LjMsd2lkdGg9NSx0cmVlaGVpZ2h0X3Jvdz0yNSx0cmVlaGVpZ2h0X2NvbD0yNSkKcGhlYXRtYXAocnIsbmFfY29sID0gImdyYXk1MCIsdHJlZWhlaWdodF9yb3c9MjUsdHJlZWhlaWdodF9jb2w9MjUpCmBgYAoKQ29tcGFyZSBwYXJlZCBhbmQgcmVndWxhciB0ZXN0cwpgYGB7cn0KeCA8LSBkZWwkSUQ7IHkgPC0gZGVsMiRwSUQ7CnggPC0gZGVsJFREOyB5IDwtIGRlbDIkcFREOyAKeCA8LSB4W3VubGlzdChsYXBwbHkoeCxpcy5saXN0KSldCnkgPC0geVt1bmxpc3QobGFwcGx5KHksaXMubGlzdCkpXQpzdW0odW5saXN0KGxhcHBseSh4LGZ1bmN0aW9uKHgpIHN1bSh4JHJlcyRwdmFsdWU8MWUtMyxuYS5ybT1UKSkpKQpzdW0odW5saXN0KGxhcHBseSh5LGZ1bmN0aW9uKHgpIHN1bSh4JHJlcyRwdmFsdWU8MWUtMyxuYS5ybT1UKSkpKQpgYGAKCiMgSW50cmEtdHVtb3JhbCBoZXRlcm9nZW5laXR5CgpgYGB7cn0Kc2NvbiA8LSBDb25vcyRuZXcocmVhZFJEUygifnBraGFyY2hlbmtvL20vc2NhZGRlbi9ibW1ldC9qYW4yMDE5L3Njb24ucmRzIikpCm5mYWMgPC0gcmVhZFJEUygiLi4vbmZhYy5yZHMiKQojbmZhYyA8LSByZWFkUkRTKCIvZDAtbWVuZGVsL2hvbWUvbWVpc2wvV29ya3BsYWNlL0JNTUUvRmlndXJlcy9kYXRhL2NlbGwuYW5vLm1lcmdlZC5yZHMiKQpgYGAKCgpgYGB7cn0KCgp0dW1vci5jZWxscyA8LSBuYW1lcyhuZmFjKVtuZmFjPT0nVHVtb3InXQoKdHQgPC0gdGFibGUoc2NvbiRnZXREYXRhc2V0UGVyQ2VsbCgpW3R1bW9yLmNlbGxzXSkKdmFsaWQuc2FtcGxlcyA8LSBuYW1lcyh0dClbdHQ+MTBdCgpjZGwgPC0gbGFwcGx5KHNjb24kc2FtcGxlc1t2YWxpZC5zYW1wbGVzXSxmdW5jdGlvbih4KSB0KHgkbWlzYyRyYXdDb3VudHNbcm93bmFtZXMoeCRtaXNjJHJhd0NvdW50cykgJWluJSB0dW1vci5jZWxscywsZHJvcD1GXSkpCm5hbWVzKGNkbCkgPC0gZ3N1YigiTm9uaW52b2x2ZWQiLCJEaXN0YWwiLG5hbWVzKGNkbCkpCmNkbCA8LSBsYXBwbHkoY2RsLGZ1bmN0aW9uKG0pIHsgY29sbmFtZXMobSkgPC0gZ3N1YigiTm9uaW52b2x2ZWQiLCJEaXN0YWwiLGNvbG5hbWVzKG0pKTsgbX0pCgp0cDIgPC0gbWNsYXBwbHkoY2RsLGJhc2ljUDJwcm9jLCAgblBjcz0zLCBnZXQudHNuZT1ULCBnZXQubGFyZ2V2aXM9RiwgbWFrZS5nZW5la25uPUYsIHRyaW09MCAsIG1pbi5jZWxscy5wZXIuZ2VuZSA9IC0xLCBuLmNvcmVzPTEsbi5vZGdlbmVzPTEwMDAsbWMuY29yZXM9MzApCgpgYGAKCgpDb25vcwpgYGB7cn0KdGNvbiA8LSBDb25vcyRuZXcodHAyLG4uY29yZXM9MSkKdGNvbiRidWlsZEdyYXBoKGsgPSAxMCxrLnNlbGYgPSA1LG5jb21wcyA9IDEwLG1ldHJpYyA9ICdMMicsbi5vZGdlbmVzID0gMTAwMCkKCiMgc3RvcmUgZGlmZmVyZW50IGVtYmVkZGluZ3MKdGNvbiRtaXNjJGVtYmVkZGluZ3MgPC0gbGlzdCgpCiN0Y29uJGVtYmVkR3JhcGgobWV0aG9kPSdVTUFQJyk7CiN0Y29uJG1pc2MkZW1iZWRkaW5ncyR1bWFwIDwtIHRjb24kZW1iZWRkaW5nCnRjb24kZW1iZWRHcmFwaChzZ2RfYmF0Y2hlcz0wLjFlOCxhcGhhPTEpOwp0Y29uJG1pc2MkZW1iZWRkaW5ncyRsdiA8LSB0Y29uJGVtYmVkZGluZwoKdGNvbiRmaW5kQ29tbXVuaXRpZXMobWV0aG9kPWxlaWRlbi5jb21tdW5pdHkscmVzb2x1dGlvbj0wLjYpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmFscGhhIDwtIDAuMzsgc2l6ZSA8LSAyOyAKdGNvbiRlbWJlZGRpbmcgPC0gdGNvbiRtaXNjJGVtYmVkZGluZ3MkbHYKbjEgPC0gdGNvbiRwbG90R3JhcGgoY29sb3IuYnk9J3NhbXBsZScsYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPVQscGxvdC5uYT1GKQojbjIgPC0gdGNvbiRwbG90R3JhcGgoZ2VuZT0nRk9TJyxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9RixwbG90Lm5hPUYpCm4yIDwtIHRjb24kcGxvdEdyYXBoKGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1ULHBsb3QubmE9RikKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QobjEsbjIpLG5yb3c9MSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD00fQp0Y29uJHBsb3RHcmFwaChjb2xvcnM9Y3NtWyw0XSxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9RixwbG90Lm5hPUYsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45NykKYGBgCgoKYGBge3IgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zfQphbHBoYSA8LSAwLjM7IHNpemUgPC0gMjsgCnRjb24kZW1iZWRkaW5nIDwtIHRjb24kbWlzYyRlbWJlZGRpbmdzJGx2Cm4xIDwtIHRjb24kcGxvdEdyYXBoKGNvbG9yLmJ5PSdzYW1wbGUnLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RikrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ3JpZ2h0JykrIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQodGl0bGU9IlNhbXBsZSIsb3ZlcnJpZGUuYWVzPWxpc3Qoc2l6ZT01KSkpCmdnc2F2ZSgndGNvbi5zYW1wbGVzLnBkZicsbjEsd2lkdGg9NC41LGhlaWdodD0zKQpuMQpgYGAKCgpgYGB7cn0KY20gPC0gZG8uY2FsbChjYmluZCxsYXBwbHkoY29ub3M6OjpyYXdNYXRyaWNlc1dpdGhDb21tb25HZW5lcyhDb25vcyRuZXcodHAyKSksZnVuY3Rpb24obSkgdChtW3Jvd25hbWVzKG0pJWluJSB0dW1vci5jZWxscywsZHJvcD1GXSkpKQp0Y3AyIDwtIGJhc2ljUDJwcm9jKGNtLCAgblBjcz0yMCwgZ2V0LnRzbmU9VCwgZ2V0LmxhcmdldmlzPUYsIG1ha2UuZ2VuZWtubj1GLCB0cmltPTEgLCBtaW4uY2VsbHMucGVyLmdlbmUgPSAwLCBuLmNvcmVzPTMwLG4ub2RnZW5lcz0zMDAwKQpgYGAKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9NX0KdGNwMiRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsZ3JvdXBzPWFzLmZhY3RvcihzY29uJGdldERhdGFzZXRQZXJDZWxsKCkpKQpgYGAKYGBge3IgZmlnLndpZHRoPTUsZmlnLmhlaWdodD01fQpjb25vczo6ZW1iZWRkaW5nUGxvdCh0Y3AyJGVtYmVkZGluZ3NbWzFdXSR0U05FLGdyb3Vwcz1hcy5mYWN0b3Ioc2NvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSxzaXplPTIpCmBgYAoKYGBge3IgZmlnLndpZHRoPTUsZmlnLmhlaWdodD01fQpjb25vczo6ZW1iZWRkaW5nUGxvdCh0Y3AyJGVtYmVkZGluZ3NbWzFdXSR0U05FLGNvbG9ycz1jc21bLDRdLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOTcpCmBgYApgYGB7ciBmaWcud2lkdGg9NSxmaWcuaGVpZ2h0PTV9CiMgZXBpdGhlbGlhbCBjZWxsIGRpZmZlcmVudGlhdGlvbgojIEVMRjMKY29ub3M6OmVtYmVkZGluZ1Bsb3QodGNwMiRlbWJlZGRpbmdzW1sxXV0kdFNORSxjb2xvcnM9dGNwMiRjb3VudHNbLCdTT1g5J10sZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45NykKYGBgCgpQcm9qZWN0IG90aGVyIGRhdGFzZXRzIHRvIHRoZSBQQ3Mgb2YgZWFjaCBkYXRhc2V0CmBgYHtyfQpjb21tb24uZ2VuZXMgPC0gUmVkdWNlKGludGVyc2VjdCwgbGFwcGx5KHRwMixmdW5jdGlvbih4KSBjb2xuYW1lcyh4JGNvdW50cykpKQoKbi50b3AucGNzIDwtIDE7CnBjbCA8LSBsYXBwbHkodHAyLGZ1bmN0aW9uKHIpIHsgCiAgeCA8LSByJG1pc2MkUENBJHZbLDE6bi50b3AucGNzLGRyb3A9Rl0KICB4IDwtIHhbcm93bmFtZXMoeCkgJWluJSBjb21tb24uZ2VuZXMsLGRyb3A9Rl0KICB4IDwtIHQodCh4KS9jb2xTdW1zKHgqeCkpCn0pCgojIHByb2plY3QKY3NtIDwtIGRvLmNhbGwocmJpbmQsbGFwcGx5KHRwMixmdW5jdGlvbihyKSB7CiAgeCA8LSByJGNvdW50czsKICAjeEB4IDwtIHhAeCpyZXAociRtaXNjW1sndmFyaW5mbyddXVtjb2xuYW1lcyh4KSwnZ3NmJ10sZGlmZih4QHApKTsgIyBhcHBseSB2YXJpYW5jZSBzY2FsaW5nCiAgY2VudGVyIDwtIE1hdHJpeDo6Y29sTWVhbnMoeCkKICBwY3MgPC0gZG8uY2FsbChjYmluZCxsYXBwbHkocGNsLGZ1bmN0aW9uKHBjbSkgewogICAgI3ogPC0gcGNtW3NhbXBsZSgxOm5yb3cocGNtKSksLGRyb3A9Rl07IHJvd25hbWVzKHopIDwtIHJvd25hbWVzKHBjbSk7IHBjbSA8LSB6OwogICAgcGNhcyA8LSB0KGFzLm1hdHJpeCh0KHhbLHJvd25hbWVzKHBjbSksZHJvcD1GXSAlKiUgcGNtKSkgLSBhcy52ZWN0b3IodChjZW50ZXJbcm93bmFtZXMocGNtKV0gJSolIHBjbSkpKQogIH0pKQogIGlmKG4udG9wLnBjcz4xKSB7IGNvbG5hbWVzKHBjcykgPC0gcGFzdGUocmVwKG5hbWVzKHBjbCksZWFjaD1uLnRvcC5wY3MpLHJlcCgxOm4udG9wLnBjcyxsZW5ndGgocGNsKSksc2VwPSdfJyl9IGVsc2UgeyBjb2xuYW1lcyhwY3MpIDwtIG5hbWVzKHBjbCl9CiAgCiAgcGNzCn0pKQoKYGBgCgpPcHRpbWl6ZSBvcmllbnRhdGlvbnM6CmBgYHtyfQoKZm9yKGkgaW4gMToxMCkgewogICBjYyA8LSBjb3IoY3NtKQogICBkaWFnKGNjKSA8LSAwOwogICAKICAgbWkgPC0gc29ydChhcHBseShjYywxLGZ1bmN0aW9uKHgpIHhbd2hpY2gubWF4KGFicyh4KSldKSkKICAgaWYobWlbMV08MCkgewogICAgIGZsaXAuaW5kIDwtIG5hbWVzKG1pKVsxXQogICAgIGNzbVssZmxpcC5pbmRdIDwtIC0xKmNzbVssZmxpcC5pbmRdCiAgIH0KfQpgYGAKCgoKVmlzdWFsaXplCgpTY29yZXMKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD02fQpyZXF1aXJlKENvbXBsZXhIZWF0bWFwKQpzbCA8LSB1bmlxdWUoZ3N1YigiLS4qIiwiIixjb2xuYW1lcyhjc20pKSk7IHNjIDwtIHNldE5hbWVzKHJhaW5ib3cobGVuZ3RoKHNsKSksc2wpCmhjIDwtIGhjbHVzdChhcy5kaXN0KDEtYWJzKGNvcihjc20sbWV0aG9kPSdzcGVhcm1hbicpKSkpCiNoYyA8LSBoY2x1c3QoYXMuZGlzdCgxLWNvcihjc20pKSkKcGxvdChoYykKY28gPC0gaGMkb3JkZXIKCkhlYXRtYXAoY3NtLCBjbHVzdGVyX3Jvd3MgPSBULCBzaG93X3Jvd19uYW1lcyA9IEYsYm9yZGVyPVQsIGNvbHVtbl9vcmRlcj1jbywKICAgICAgICBib3R0b21fYW5ub3RhdGlvbj1IZWF0bWFwQW5ub3RhdGlvbihzYW1wbGU9ZmFjdG9yKHNldE5hbWVzKGdzdWIoIi0uKiIsIiIsY29sbmFtZXMoY3NtKSksY29sbmFtZXMoY3NtKSksbGV2ZWxzPXNsKSxjb2w9bGlzdChzYW1wbGU9c2MpKSwKICAgICAgICByaWdodF9hbm5vdGF0aW9uID0gSGVhdG1hcEFubm90YXRpb24od2hpY2g9J3Jvdycsc2FtcGxlPWZhY3RvcihzZXROYW1lcyhnc3ViKCItLioiLCIiLHJvd25hbWVzKGNzbSkpLHJvd25hbWVzKGNzbSkpLGxldmVscz1zbCksY29sPWxpc3Qoc2FtcGxlPXNjKSkKICAgICAgICApCmBgYAoKCkZpeCByZW1haW5pbmcgZmxpcHMKYGBge3J9CmNzbVssY29bYygyLDMpXV0gPC0gLWNzbVssY29bYygyLDMpXV0KY3NtWyxjb1tjKDc6MTApXV0gPC0gLWNzbVssY29bYyg3OjEwKV1dCmNzbVssY29bYygxKV1dIDwtIC1jc21bLGNvW2MoMSldXQpgYGAKCgoKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9N30KI3ggPC0gY3NtOwojZGVwdGggPC0gTWF0cml4Ojpjb2xTdW1zKGNtKVtyb3duYW1lcyhjc20pXTsKI2RlcHRoIDwtIGxvZzEwKGRlcHRoKTsgZGVwdGggPC0gKGRlcHRoLW1lYW4oZGVwdGgpKSpyYW5nZShkaWZmKGRlcHRoKSkqMgojY3NtIDwtIGNiaW5kKGNzbSxkZXB0aD1kZXB0aCkKCmhtIDwtIEhlYXRtYXAoY3NtLCBuYW1lPSdQQyBzY29yZXMnLCBjbHVzdGVyX3Jvd3MgPSBULCBzaG93X3Jvd19uYW1lcyA9IEYsYm9yZGVyPVQsIGNsdXN0ZXJfY29sdW1ucyA9IGhjLAogICAgICAgICNib3R0b21fYW5ub3RhdGlvbj1IZWF0bWFwQW5ub3RhdGlvbihzYW1wbGU9ZmFjdG9yKHNldE5hbWVzKGdzdWIoIi0uKiIsIiIsY29sbmFtZXMoY3NtKSksY29sbmFtZXMoY3NtKSksbGV2ZWxzPXNsKSxjb2w9bGlzdChzYW1wbGU9c2MpKSwKICAgICAgICAjcmlnaHRfYW5ub3RhdGlvbiA9IEhlYXRtYXBBbm5vdGF0aW9uKHdoaWNoPSdyb3cnLHNhbXBsZT1mYWN0b3Ioc2V0TmFtZXMoZ3N1YigiLS4qIiwiIixyb3duYW1lcyhjc20pKSxyb3duYW1lcyhjc20pKSxsZXZlbHM9c2wpLGNvbD1saXN0KHNhbXBsZT1zYykpLAogICAgICAgIGNvbD1jaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC0zLCAwLCAzKSwgYygnZGFya2dyZWVuJywnZ3JleTkwJywnb3JhbmdlJykpLAogICAgICAgIHJvd19zcGxpdCA9IGFzLmZhY3RvcihzZXROYW1lcyhnc3ViKCJfLioiLCIiLHJvd25hbWVzKGNzbSkpLHJvd25hbWVzKGNzbSkpKSxyb3dfdGl0bGVfcm90PTAsCiAgICAgICAgdXNlX3Jhc3RlciA9IFQscmFzdGVyX2RldmljZSA9ICJDYWlyb1BORyIpCgpwZGYoZmlsZT0ncGMuaGVhdG1hcC5wZGYnLHdpZHRoPTUsaGVpZ2h0PTcpOyBwcmludChobSk7IGRldi5vZmYoKQpobQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00LjR9CmFscGhhIDwtIDAuMzsgc2l6ZSA8LSAyOwpwbCA8LSBhcHBseShjc20sMixmdW5jdGlvbihkKSB0Y29uJHBsb3RHcmFwaChjb2xvcnM9ZCxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9RixwbG90Lm5hPUYsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45NSxwYWxldHRlPWNvbG9yUmFtcFBhbGV0dGUoYygnZGFya2dyZWVuJywnZ3JleTkwJywnb3JhbmdlJyksc3BhY2U9J0xhYicpKSkgJT4lIAogICAgbWFwcGx5KGZ1bmN0aW9uKHgseSkgeCtnZ3RpdGxlKHkpKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTYpKSwuLGNvbG5hbWVzKGNzbSksIFNJTVBMSUZZPUYpCiNwbCA8LSBtYXBwbHkoZnVuY3Rpb24oeCx5KSB7IHgrZ2d0aXRsZSh5KX0scGwsY29sbmFtZXMoY3NtKSxTSU1QTElGWT1GKQpwbCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9cGxbY29dLG5yb3c9MikKcGRmKGZpbGU9J3Rjb24uUENzLnBkZicsd2lkdGg9MTAsaGVpZ2h0PTQuNCk7IHByaW50KHBsKTsgZGV2Lm9mZigpOwpwbApgYGAKCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NC40fQphbHBoYSA8LSAwLjg7IHNpemUgPC0gNDsKcGwgPC0gdGNvbiRwbG90UGFuZWwoY29sb3JzPWNzbVssJ0JNRVQxMC1UdW1vciddLHBhbGV0dGU9Y29sb3JSYW1wUGFsZXR0ZShjKCdkYXJrZ3JlZW4nLCdncmV5OTAnLCdvcmFuZ2UnKSxzcGFjZT0nTGFiJyksc2l6ZT1zaXplLGFscGhhPWFscGhhLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOTUsbnJvdz0yLHRpdGxlLnNpemU9MCxyZXR1cm4ucGxvdGxpc3Q9VCkKcGwgPC0gbWFwcGx5KGZ1bmN0aW9uKHgseSkgeCtnZ3RpdGxlKHkpKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTYpKSxwbCxuYW1lcyh0Y29uJHNhbXBsZXMpLFNJTVBMSUZZPUYpOwpwcCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9cGxbY29dLG5yb3c9MikKcGRmKGZpbGU9J3RTTkUuQk1FVDEwX1BDLnBkZicsd2lkdGg9MTAsaGVpZ2h0PTQuNCk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQphbHBoYSA8LSAwLjg7IHNpemUgPC0gMjsKI3NpZzEgSUVSMiwgU09YOSwgU09YNCwgRk9TPyBKVU5CICAgQlJEMiwgQUhOQUsKI3NpZzIgTVQtQ08yLCBNVC1DTzEKdGNvbiRwbG90UGFuZWwoZ2VuZT0nVUJFMlQnLHNpemU9c2l6ZSxhbHBoYT1hbHBoYSxncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1LG5yb3c9Mix0aXRsZS5zaXplPTQpCmBgYAoKYGBge3IgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9M30KIyBNVC1DTzIKdGNvbiRwbG90R3JhcGgoZ2VuZT0nU1RBVDMnLGFscGhhPTAuMyxzaXplPTIsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45NSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MiwgZmlnLmhlaWdodD01fQpnbmwgPC0gYygnSUVSMicsJ0pVTkInLCdTT1g0JywnQlJEMicsJ0NMRE40JywKICAgICAgICAgJ1NUTU4xJywnSDJBRlonLCdVQkUyVCcsCiAgICAgICAgICdNVC1DTzEnLCdNVC1ORDEnKQpwbCA8LSBsYXBwbHkoZ25sLGZ1bmN0aW9uKGcpIHRjb24kcGxvdEdyYXBoKGdlbmU9ZyxhbHBoYT0wLjMsc2l6ZT0yLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOTUpK2dndGl0bGUoZykrIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xOCkpKQpwcCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9cGxbYXMudmVjdG9yKHQobWF0cml4KDE6MTAsbmNvbD0yKSkpXSxuY29sPTIpCnBkZihmaWxlPSd0Y29uLmdlbmVzLnBkZicsd2lkdGg9NCxoZWlnaHQ9MTEpOyBwcmludChwcCk7IGRldi5vZmYoKTsKcHAKYGBgCgpFeHByZXNzaW9uIG9uIHRoZSBzYW1wbGUtc3BlY2lmaWMgdFNORXMKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTN9CmducyA8LSBjKCJJRVIyIiwiSlVOQiIsJ1NPWDQnLCdTVE1OMScsJ0gyQUZaJywnVUJFMlQnKQpnbnMgPC0gYygiSUVSMiIsJ1NPWDQnLCdVQkUyVCcpCmFscGhhIDwtIDAuODsgc2l6ZSA8LSAzCnBsIDwtIGxhcHBseShnbnMsZnVuY3Rpb24oZykgeyAKICBwbCA8LSB0Y29uJHBsb3RQYW5lbChnZW5lPWcsc2l6ZT1zaXplLGFscGhhPWFscGhhLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOTUsbnJvdz0xLHRpdGxlLnNpemU9MCxyZXR1cm4ucGxvdGxpc3Q9VClbY29dOwogICNwbCA8LSBtYXBwbHkoZnVuY3Rpb24oeCx5KSB4K2dndGl0bGUoeSkrIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xNikpLHBsLG5hbWVzKHRjb24kc2FtcGxlcyksU0lNUExJRlk9Rik7Cn0pCgpwcCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9dW5saXN0KHBsLHJlY3Vyc2l2ZT1GKSxucm93PWxlbmd0aChnbnMpKQpwZGYoZmlsZT0ndFNORS5nZW5lcy5wZGYnLHdpZHRoPTIwLGhlaWdodD02KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CnBwCmBgYAoKCgpQQyBvdmVybGFwCmBgYHtyfQpsaXN0VG9PdmVybGFwTWF0cml4Mj1mdW5jdGlvbihyZXMsY291bnRzPU5VTEwpewoKICBsYz1sZW5ndGgocmVzKQogIHN0YXQ9bWF0cml4KHJlcCgwLGxjKmxjKSxsYyxsYykKICBmb3IoIGkgaW4gc2VxKGxjKSl7CiAgICBmb3IgKGogaW4gc2VxKGxjKSl7CiAgICAgIGlmKCBpIT1qKXsKICAgICAgICBzdGF0W2ksal09bGVuZ3RoKGludGVyc2VjdChyZXNbW2ldXSxyZXNbW2pdXSkpL2xlbmd0aCh1bmlvbihyZXNbW2ldXSxyZXNbW2pdXSkpCiAgICAgICAgaWYgKCFpcy5udWxsKGNvdW50cykpe3N0YXRbaSxqXT1sZW5ndGgoaW50ZXJzZWN0KHJlc1tbaV1dLHJlc1tbal1dKSkgfQogICAgICB9CiAgICB9CiAgfQogIGNvbG5hbWVzKHN0YXQpPW5hbWVzKHJlcykKICByb3duYW1lcyhzdGF0KT1uYW1lcyhyZXMpCiAgcmV0dXJuKHN0YXQpCiAgCn0KdG9wZ25sIDwtIGxhcHBseShwY2wsZnVuY3Rpb24oeCkgbmEub21pdCgocm93bmFtZXMoeClbb3JkZXIoYWJzKHhbLDFdKSxkZWNyZWFzaW5nID1UKV0pWzE6MjAwXSkpCm9tIDwtIGxpc3RUb092ZXJsYXBNYXRyaXgyKHRvcGdubCk7IGRpYWcob20pIDwtIE5BOwpgYGAKCmBgYHtyIGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9NX0KI3BoZWF0bWFwKHJyLG5hX2NvbCA9ICJncmF5NTAiLGZpbGVuYW1lPSdkZS5JQi5vdmVybGFwLnBkZicsaGVpZ2h0PTQuMyx3aWR0aD01LHRyZWVoZWlnaHRfcm93PTI1LHRyZWVoZWlnaHRfY29sPTI1KQpobSA8LSBwaGVhdG1hcDo6cGhlYXRtYXAob20sbmFfY29sID0gImdyYXk1MCIsdHJlZWhlaWdodF9yb3c9MjUsdHJlZWhlaWdodF9jb2w9MjUpCnBkZihmaWxlPSdvdmVybGFwLmhlYXRtYXAucGRmJyx3aWR0aD02LGhlaWdodD01KTsgcHJpbnQoaG0pOyBkZXYub2ZmKCk7CmhtCmBgYAoKYGBge3J9CmludGVyc2VjdCh0b3BnbmxbWydCTUVUMS1UdW1vciddXSx0b3BnbmxbWydCTUVUNS1UdW1vciddXSkKYGBgCgpgYGB7cn0KaW50ZXJzZWN0KHRvcGdubFtbJ0JNRVQxMC1UdW1vciddXSx0b3BnbmxbWydCTUVUNS1UdW1vciddXSkKYGBgCgpDYWxjdWxhdGUgZ28gZW5yaWNobWVudHMKYGBge3J9CnRvcGdubCA8LSBsYXBwbHkocGNsLGZ1bmN0aW9uKHgpIChyb3duYW1lcyh4KVtvcmRlcihhYnMoeFssMV0pLGRlY3JlYXNpbmcgPVQpXSlbMToyMDBdKQp0b3BnbmwkaW50ZXJzZWN0IDwtIGludGVyc2VjdCh0b3BnbmxbWydCTUVUMTAtVHVtb3InXV0sdG9wZ25sW1snQk1FVDUtVHVtb3InXV0pCiN0b3BnbmwgPC0gdW5saXN0KGxhcHBseSgxOm5jb2wocGNsW1sxXV0pLGZ1bmN0aW9uKGkpIHNldE5hbWVzKGxhcHBseShwY2wsZnVuY3Rpb24oeCkgKHJvd25hbWVzKHgpW29yZGVyKGFicyh4WyxpXSksZGVjcmVhc2luZyA9VCldKVsxOjIwMF0pICxwYXN0ZShuYW1lcyhwY2wpLGksc2VwPSdfJykpKSxyZWN1cnNpdmU9RikKI3RvcGdubCRpbnRlcnNlY3QgPC0gaW50ZXJzZWN0KHRvcGdubFtbJ0JNRVQxMC1UdW1vcl8xJ11dLHRvcGdubFtbJ0JNRVQ1LVR1bW9yXzEnXV0pCgp0b3BnbmwkYWxsIDwtIGNvbW1vbi5nZW5lczsKdG9wZ25sLmVudHJleiA8LSBsYXBwbHkodG9wZ25sLCBiaXRyLCAnU1lNQk9MJywgJ0VOVFJFWklEJywgb3JnLkhzLmVnLmRiKSAlPiUgbGFwcGx5KGBbW2AsICJFTlRSRVpJRCIpCmNvbW1vbi5nZW5lcy5lbnRyZXogPC0gdG9wZ25sLmVudHJleiRhbGw7IHRvcGdubC5lbnRyZXokYWxsIDwtIE5VTEw7Cgp0b3AuZ29zIDwtIGxhcHBseSh0b3BnbmwuZW50cmV6LGVucmljaEdPT3B0LHVuaXZlcnNlPWNvbW1vbi5nZW5lcy5lbnRyZXosIG9udD0nQlAnLCBnb0RhdGE9Z29fZGF0YXNbWydCUCddXSwgcmVhZGFibGU9VCwgT3JnREI9b3JnLkhzLmVnLmRiKQpgYGAKCgpgYGB7cn0KY3ggPC1nb3MuY2x1c3Rlcih0b3AuZ29zWy1ncmVwKCdpbnRlcnNlY3QnLG5hbWVzKHRvcC5nb3MpKV0sbi5jbHVzdGVycz0xMCxtYXgucHZhbD0wLjA1KQpgYGAKCmBgYHtyIGZpZy53aWR0aD04LGZpZy5oZWlnaHQ9NX0KbXUgPC0gSGVhdG1hcChhcy5tYXRyaXgoY3gkc3VtbWFyeSksY29sPWNvbHMkdXAsYm9yZGVyPVQsc2hvd19yb3dfZGVuZD1GLHNob3dfY29sdW1uX2RlbmQ9RiwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ1ogc2NvcmUnKSwgcm93X25hbWVzX21heF93aWR0aCA9IHVuaXQoOCwgImNtIikscm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSwpCnBkZihmaWxlPSd0dW1vci5wYy5mdW5jdGlvbnMucGRmJyx3aWR0aD03LGhlaWdodD00LjUpOyBkcmF3KG11LCBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImxlZnQiKTsgZGV2Lm9mZigpOwptdQpgYGAKCgpgYGB7cn0KbmFtZXMoY3gkY2x1c3RlcnMpW2N4JGNsdXN0ZXJzPT1jeCRjbHVzdGVyWydlcGl0aGVsaWFsIGNlbGwgZGlmZmVyZW50aWF0aW9uJ11dCmBgYAoKYGBge3J9CmN4JGpvaW50W2N4JGpvaW50JERlc2NyaXB0aW9uPT0nZXBpdGhlbGlhbCBjZWxsIGRpZmZlcmVudGlhdGlvbicsXQpgYGAKCmBgYHtyfQpjeCRqb2ludFtjeCRqb2ludCREZXNjcmlwdGlvbj09J3RyYW5zZm9ybWluZyBncm93dGggZmFjdG9yIGJldGEgcmVjZXB0b3Igc2lnbmFsaW5nIHBhdGh3YXknLF0KYGBgCgpgYGB7cn0KY3gkam9pbnRbY3gkam9pbnQkRGVzY3JpcHRpb249PSd2YXNjdWxhdHVyZSBkZXZlbG9wbWVudCcsXQpgYGAKCgpgYGB7cn0KY3gkam9pbnRbY3gkam9pbnQkRGVzY3JpcHRpb249PSJzdHJlc3MgcmVzcG9uc2UgdG8gbWV0YWwgaW9uIixdCmBgYAoKQ2x1c3RlcmluZy1iYXNlZCBhbmFseXNpcwoKYGBge3J9CnRjb24kZmluZENvbW11bml0aWVzKG1ldGhvZD1sZWlkZW4uY29tbXVuaXR5LHJlc29sdXRpb249MC42KQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQphbHBoYSA8LSAwLjM7IHNpemUgPC0gMjsgCnRjb24kZW1iZWRkaW5nIDwtIHRjb24kbWlzYyRlbWJlZGRpbmdzJGx2Cm4xIDwtIHRjb24kcGxvdEdyYXBoKGNvbG9yLmJ5PSdzYW1wbGUnLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1ULHBsb3QubmE9RikKI24yIDwtIHRjb24kcGxvdEdyYXBoKGdlbmU9J0ZPUycsYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPUYscGxvdC5uYT1GKQpuMiA8LSB0Y29uJHBsb3RHcmFwaChhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KG4xLG4yKSxucm93PTEpCmBgYAoKYGBge3J9CnRmYWMgPC0gdGNvbiRjbHVzdGVycyRsZWlkZW4kZ3JvdXBzCmxldmVscyh0ZmFjKSA8LSBjKCcxJywnMicsJzMnLCczJykKYGBgCgpgYGB7cn0KdGNvbi5kZSA8LSB0Y29uJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz10ZmFjLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MTIsZmlnLmhlaWdodD0xMn0KI3NvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKIywgY29sdW1uLm1ldGFkYXRhLmNvbG9ycyA9IGxpc3QoY2x1c3RlcnM9dHlwZWZjLnBhbCkKcHAgPC0gcGxvdERFaGVhdG1hcCh0Y29uLHRmYWMsdGNvbi5kZSxuLmdlbmVzLnBlci5jbHVzdGVyID0gMjAgLHNob3cuZ2VuZS5jbHVzdGVycz1ULGNvbHVtbi5tZXRhZGF0YT1saXN0KHNhbXBsZXM9dGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSxvcmRlci5jbHVzdGVycyA9IFQsdXNlX3Jhc3RlciA9IFQscmFzdGVyX2RldmljZSA9ICJDYWlyb1BORyIpCiNwZGYoZmlsZT0nYW5ub3QuaGVhdG1hcC5wZGYnLHdpZHRoPTEwLGhlaWdodD0zMCk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCkNhbGN1bGF0ZSBlbnJpY2htZW50cwpgYGB7cn0KdGdvcyA8LSBjYWxjdWxhdGUuZ29zKGxhcHBseSh0Y29uLmRlLGZ1bmN0aW9uKGRlKSB7IGRlJHB2YWx1ZSA8LSBkZSRQVmFsdWU7IGRlJHBhZGogPC0gZGUkUEFkajsgZGUkbG9nMkZvbGRDaGFuZ2UgPC0gZGUkTTsgcm93bmFtZXMoZGUpIDwtIGRlJEdlbmU7IGxpc3QocmVzPWRlKSB9KSxuLnRvcC5nZW5lcyA9IDEwMCkKYGBgCgpDYWxjdWxhdGUgYW5kIHBsb3QgY2x1c3RlcnM6CmBgYHtyfQpjb2xzIDwtIGxpc3QodXA9Y29sb3JSYW1wMihjKDAsIDYpLCBjKCJncmV5OTgiLCAicmVkIikpLGRvd249Y29sb3JSYW1wMihjKDAsIDYpLCBjKCJncmV5OTgiLCAiYmx1ZSIpKSkKbi5jbHVzdGVycyA8LSAyMDsgbWF4LnB2YWwgPC0gMC4wNTsKYGBgCgoKYGBge3J9CmN4IDwtIGxhcHBseSh0Z29zLGdvcy5jbHVzdGVyLG4uY2x1c3RlcnM9MTAsbWF4LnB2YWw9bWF4LnB2YWwpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsZmlnLmhlaWdodD01fQptdSA8LSBIZWF0bWFwKGFzLm1hdHJpeChjeCR1cCRzdW1tYXJ5KSxjb2w9Y29scyR1cCxib3JkZXI9VCxjbHVzdGVyX2NvbHVtbnM9RixzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApLCkKI21kIDwtIEhlYXRtYXAoYXMubWF0cml4KGN4JGRvd24kc3VtbWFyeSksY29sPWNvbHMkZG93bixib3JkZXI9VCxzaG93X3Jvd19kZW5kPUYsc2hvd19jb2x1bW5fZGVuZD1GLCBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAnWiBzY29yZScpLCByb3dfbmFtZXNfbWF4X3dpZHRoID0gdW5pdCg4LCAiY20iKSxyb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApKQojcGRmKGZpbGU9J2RlLklCLnVwLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtdSwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKI3BkZihmaWxlPSdkZS5JQi5kb3duLnBkZicsd2lkdGg9NyxoZWlnaHQ9NC41KTsgZHJhdyhtZCwgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Iik7IGRldi5vZmYoKTsKcGRmKGZpbGU9J3Rjb24uY2x1c3Rlci5nby5wZGYnLHdpZHRoPTUsaGVpZ2h0PTIuNSk7IHByaW50KG11KTsgZGV2Lm9mZigpOwptdQpgYGAKCgpgYGB7cn0KbjIgPC0gdGNvbiRwbG90R3JhcGgoZ3JvdXBzPXRmYWMsYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPVQscGxvdC5uYT1GLGZvbnQuc2l6ZT1jKDEwLDEwKSkKZ2dzYXZlKCd0Y29uLmNsdXN0ZXJzLnBkZicsbjIsd2lkdGg9MyxoZWlnaHQ9MykKbjIKYGBgCgoKQSBzbWFsbCB2ZXJzaW9uIG9mIHRoZSBoZXRtYXAsIGZvciB0aGUgbWFpbiBmaWd1cmUKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD04fQpzb3VyY2UoIn4vbS9wMi9jb25vcy9SL3Bsb3QuUiIpCmdlbmVzIDwtIGMoJ01TNEExJywnQ0Q3OUEnLCdDRDc5QicsJ01aQjEnLCdWUFJFQjMnLCdTRUMxMUMnLCdJR0xMNScsJ0dOTFknLCdHWk1CJywnTElMUkE0JywnQVpVMScsJ01QTycsJ0ZDTjEnLCdJRVIzJywnQzVBUjEnLCdJR0onLCdTT1g0JywnU1RNTjEnLCdNWUw5JywnSEJEJywnR1pNSycsJ0FSJywnS0xLMicpCnBwIDwtIHBsb3RERWhlYXRtYXAoc2NvbixuZmFjLGFubm90LmRlLG4uZ2VuZXMucGVyLmNsdXN0ZXIgPSAyMCAsc2hvdy5nZW5lLmNsdXN0ZXJzPVQsIGNvbHVtbi5tZXRhZGF0YS5jb2xvcnMgPSBsaXN0KGNsdXN0ZXJzPXR5cGVmYy5wYWwpLCBvcmRlci5jbHVzdGVycyA9IFQsIGFkZGl0aW9uYWwuZ2VuZXMgPSBnZW5lcywgbGFiZWxlZC5nZW5lLnN1YnNldCA9IGdlbmVzLCBtaW4uYXVjID0gMC42LHVzZV9yYXN0ZXIgPSBULHJhc3Rlcl9kZXZpY2UgPSAiQ2Fpcm9QTkciKQpwcApgYGAK